Vue路由history模式刷新404怎么解决

Vue history 模式部署后,在子页面刷新或直接访问就 404。根源是前端路由的假路径被服务器当成真文件路径去找,找不到就报错。Nginx 用 try_files $uri $uri/ /index.html 兜底返回首页,把路由交还给 Vue。记得再配个通配路由处理真正不存在的页面。

Vue 项目本地跑得好好的,路由跳转丝滑无比。一部署到服务器,在某个子页面按个 F5 刷新,啪,404。

这个坑我信了,几乎每个做 Vue/React 单页应用的人都踩过。

它跟 Nginx 的 try_files 配置有关,根子在于前端路由和后端路由的"认知错位"。这篇把 404 怎么来的、try_files 怎么救、原理是什么,一次讲透。

现象复现

一个用了 history 模式路由的 Vue 项目,部署到 Nginx 后:

  • 从首页点链接跳到 /user/list —— 正常。
  • 停在 /user/list 这个页面按 F5 刷新 —— 404 Not Found。
  • 直接在地址栏敲 域名/user/list 回车 —— 404。

规律很清楚:页面内跳转没事,一刷新或直接访问子路径就 404。

要搞懂为什么,得先说清 Vue 路由的两种模式。

hash 模式 vs history 模式

Vue Router 有两种模式,区别就在 URL 长什么样。

hash 模式,地址里带个 #:

https://mebugs.com/#/user/list
​

# 后面的部分(/user/list)叫 hash,它有个特性:浏览器不会把 hash 发给服务器。服务器永远只看到 https://mebugs.com/,返回首页就行,路由全靠前端 JS 根据 hash 自己切。所以 hash 模式天然不会 404。

history 模式,地址干净,没有 #:

https://mebugs.com/user/list
​

好看是好看,但问题来了:这个 /user/list 是真的会发给服务器的。

404 到底怎么来的

关键就在 history 模式这个"真路径"上。

页面内跳转时,是 Vue Router 用 H5 的 history.pushState 在前端改地址栏,根本没请求服务器,所以没事。

但你一按 F5 刷新,浏览器会拿着当前这个完整地址 https://mebugs.com/user/list 去向服务器要东西。

服务器(Nginx)收到请求一看:/user/list?我这目录下没有叫 user/list 的文件或文件夹啊。找不到,返回 404。

这就是认知错位:

  • 前端以为 /user/list 是"路由",该由 Vue 处理。
  • 服务器以为 /user/list 是"文件路径",去磁盘找文件,没找到就 404。

实际上整个 SPA 只有一个真实文件 index.html,所有路由都该交给它里面的 Vue 去解析。

try_files 怎么救

解法的核心思路:不管用户访问什么路径,只要服务器找不到对应的真实文件,就统统返回 index.html,让 Vue 接管后续路由。

Nginx 实现这个,靠的就是 try_files 指令。

server {
    listen 80;
    server_name mebugs.com;
    # 前端打包后的 dist 目录
    root /usr/share/nginx/html;

    location / {
        # 核心就这一行
        try_files $uri $uri/ /index.html;
    }
}
​

重点是 try_files $uri $uri/ /index.html; 这行,它的逻辑是按顺序试:

  1. $uri:先把请求路径当文件找。比如请求 /logo.png,有这个文件就直接返回它。
  2. $uri/:当文件找不到,再当目录找。
  3. /index.html:前两个都没命中,兜底返回 index.html。

所以当请求 /user/list 时,前两步都找不到(没这文件也没这目录),走到第三步返回 index.html。浏览器拿到首页,Vue 启动,一看地址是 /user/list,前端路由把对应页面渲染出来。404 解决。

这行配置的精髓就是那个兜底的 /index.html,把所有"找不到文件"的请求都导向 SPA 入口。

别把真 404 也吞了

try_files 这么配有个副作用得注意:现在任何不存在的路径都会返回 index.html,包括用户真的敲错的乱路径。

一般这问题不大,Vue Router 内部可以配一个 * 通配路由指向自己的 404 页面,由前端来展示"页面不存在":

// Vue Router 里配一个兜底路由,交给前端展示 404
const routes = [
  // ... 你的业务路由
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }
]
​

这样服务器统一返回 index.html,到底是不是有效路由,由 Vue 自己判断:是业务路由就正常渲染,不是就渲染前端的 404 页。职责清晰。

其他服务器怎么配

换个服务器,思路一样,只是指令不同。顺手记一下常见的几个。

Apache,用 .htaccess 加重写规则:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    # 已存在的文件/目录直接放行
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    # 其余全部重写到 index.html
    RewriteRule . /index.html [L]
</IfModule>
​

Node(Express)后端托管前端时:

const history = require('connect-history-api-fallback')
// 这个中间件干的就是 try_files 同样的活:找不到就回 index.html
app.use(history())
app.use(express.static('dist'))
​

万变不离其宗:静态资源正常给,其余路径兜底回 index.html。

小结

Vue history 模式刷新 404,根源是前端路由的"假路径"被当成了服务器的"真文件路径"去找,找不到就 404。

Nginx 的解法是 try_files $uri $uri/ /index.html;:先按文件找、再按目录找、都没有就兜底返回 index.html,把路由交还给 Vue。

记得在 Vue Router 里配个通配兜底路由,让前端自己处理真正不存在的页面。换 Apache、Node 道理一样,就是"文件优先,其余回首页"。

想偷懒彻底躲开这问题,也可以用 hash 模式——带 # 的地址服务器根本收不到路由部分,天生不 404,代价是 URL 不好看。按需求取舍就行。

更多推荐

章节目录