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; 这行,它的逻辑是按顺序试:
$uri:先把请求路径当文件找。比如请求/logo.png,有这个文件就直接返回它。$uri/:当文件找不到,再当目录找。/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 不好看。按需求取舍就行。
