React、VUE路由为history模式时刷新404


要了解为什么会刷新404,需要先来了解下前端路由两种模式:hash与history。

路由两种模式:hash与history

SPA需要在不刷新页面的情况下做页面更新的能力,这就需要引入前端路由,实际上,前端路由是利用了浏览器的hash或history属性。

hash :

hash (url中#后面的部分)虽然出现在 URL 中,但不会被包含在 http 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。

当hash改变时,会触发hashchange事件,监听该事件,对页面进行更新。

window.onhashchange = function() {
    
    document.getElementById("demo").innerHTML = x;    //更新页面内容

        ......

};

history :

history 利用了 html5 history interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器记录栈,

在当前已有的 back、forward、go 基础之上,它们提供了对历史记录 修改的功能(pushState将传入url直接压入历史记录栈,replaceState将传入url替换当前历史记录栈)。

只是当它们执行修改时,虽然改变了当前的 URL ,但浏览器不会立即向后端发送请求。

pushState()、replaceState() 方法接收三个参数:stateObj、title、url

history.pushState({color: "red"}, "red", "red");//设置状态


window.onpopstate = function(event) {  //监听状态

    if(event.state && event.state.color === "red") {

        document.body.style.color = "red";    //更新页面内容

    }

}

// 改变状态  *在使用history API改变浏览器的url时,仍需要额外的步骤去触发 popstate 事件,例如调用 history.back() 和 history.forward() 等方法

history 对比  hash 

优势:

pushState 设置的 url 可以是同源下的任意 url ;而 hash 只能修改 # 后面的部分,因此只能设置当前 url 同文档的 url。

pushState 设置的新的 url 可以与当前 url 一样,这样也会把记录添加到栈中;hash 设置的新值不能与原来的一样,一样的值不会触发动作将记录添加到栈中。

pushState 通过 stateObject 参数可以将任何数据类型添加到记录中;hash 只能添加短字符串。

pushState 可以设置额外的 title 属性供后续使用。

劣势:

history 在刷新页面时,如果服务器中没有相应的响应或资源,就会出现404。因此,如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

hash 模式下,仅 # 之前的内容包含在 http 请求中,对后端来说,即使没有对路由做到全面覆盖,也不会报 404。

解决方案:

Apache

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>

除了 mod_rewrite,你也可以使用 FallbackResource。

nginx

location / {
  try_files $uri $uri/ /index.html;
}

原生 Node.js

const http = require('http')
const fs = require('fs')
const httpPort = 80

http.createServer((req, res) => {
  fs.readFile('index.htm', 'utf-8', (err, content) => {
    if (err) {
      console.log('We cannot open "index.htm" file.')
    }

    res.writeHead(200, {
      'Content-Type': 'text/html; charset=utf-8'
    })

    res.end(content)
  })
}).listen(httpPort, () => {
  console.log('Server listening on: http://localhost:%s', httpPort)
})

基于 Node.js 的 Express
对于 Node.js/Express,请考虑使用 connect-history-api-fallback 中间件。

Internet Information Services (IIS)
安装 IIS UrlRewrite
在你的网站根目录中创建一个 web.config 文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Handle History Mode and custom 404/500" stopProcessing="true">
          <match url="(.*)" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="/" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Caddy

rewrite {
    regexp .*
    to {path} /
}

Firebase 主机
在你的 firebase.json 中加入:

{
  "hosting": {
    "public": "dist",
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

警告

给个警告,因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 SPA 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。
或者,如果你使用 Node.js 服务器,你可以用服务端路由匹配到来的 URL,并在没有匹配到路由的时候返回 404,以实现回退。

声明:初心|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - React、VUE路由为history模式时刷新404


愿你勿忘初心,并从一而终