编程 history 模式上线后深链接刷新 404:Nginx 回退、/api 被吞与 base 配错的排查

2026-10-23 00:04:15

history 模式上线后深链接刷新 404:Nginx 回退、/api 被吞与 base 配错的排查

现象

本地 dev 一切正常,页面内点链接跳转也正常。上线后把详情页链接发给同事,对方直接打开就是 404;自己在详情页按 F5,同样 404。同一个团队里用 hash 模式的内部后台,从来没出现过这个问题。

这类问题不是路由写错了,而是「服务器不认识前端路由」。下面按请求链路拆开看。

机制

hash 与 history 的请求差异

hash 模式(Vue Router 4 的 createWebHashHistory,Vue Router 3 的 mode: 'hash' 是默认值)里,# 之后的部分不会进入 HTTP 请求。服务器永远只看到 /index.html,所以不可能 404。代价是 URL 带 #、服务端不知道用户访问的是哪个路由因而做不了 SSR、referer 头不带 hash、SEO 差。

history 模式(createWebHistory)用 pushState/replaceState 改地址栏,用 popstate 响应前进后退。URL 干净,但深链接被直接访问时,服务器必须懂得把 /user/123 这类路径回退到 index.html。

参考:Vue Router — HTML5 History 模式

Nginx 回退

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

几个容易踩的点:

  • $uri/ 会让真实存在的目录命中目录索引。目录下没配 index 时会给 403 或 autoindex 列表,不会走到回退。SPA 项目里通常只留 $uri 和 /index.html:

    location / {
        try_files $uri /index.html;
    }
    
  • 顺序:try_files 之前的 location 精确匹配优先,所以 /api 要单独写 location,且不能落到回退里。

  • 不要用 rewrite . /index.html 这类写法,它会把 POST body 丢掉或把请求改成 GET。

  • 回退生效后,所有不存在的路径都返回 200,服务器不再报 404。前端必须补 catch-all 路由,并放在路由表最后(Vue Router 3 是 path: '*'):

    { path: '/:pathMatch(.*)*', component: NotFound }
    

/api 被回退吞掉

典型症状:前端 fetch('/api/user') 报 Unexpected token '<', "<!DOCTYPE"...,或 JSON.parse 失败。原因是 /api/user 不存在时被回退成 index.html 且状态码 200,请求「成功」了,拿到的是 HTML。

排查:

curl -i https://site/api/xxx

看 content-type 是不是 text/html。修法是给 api 前缀单独开 location,不让它进回退:

location ^~ /api/ {
    proxy_pass http://backend;
}

或者在 try_files 中对 api 前缀单独 =404。

子路径 base

部署在 https://example.com/admin/ 下时,两处都要改:

// vite.config.js
export default { base: '/admin/' }

// router
createWebHistory('/admin/')

只改一边的现象:路由跳转变成了 /user 而不是 /admin/user;或者 index.html 里的 /assets/index-xxx.js 404,因为资源被打到了根路径。

相对路径 ./ 在 history 深链接下会按当前路由路径解析,容易出错,所以 Vite 默认输出绝对路径。不要为了兼容子路径去手改成 ./。

静态托管与 CDN

S3/OSS 静态托管没有 try_files,要把错误文档(404/403)指向 index.html。

Cloudflare Pages、Netlify 用 _redirects 或 netlify.toml:

/*  /index.html  200

必须写 200,写 301 会丢 POST,地址栏还会跳一下。

index.html 必须 no-cache 或短缓存。否则新版本发布后,旧 index.html 还引用已被清掉的 assets/index-<旧hash>.js,浏览器报 Failed to load module script 或直接白屏。assets 目录可以长缓存。

分型排查

按症状对号入座,比从头读配置快:

症状大概率原因
深链接和新开窗口 404,页面内跳转正常Nginx/托管平台缺回退
只有子目录部署时才 404,资源也 404base 与 createWebHistory() 只改了一边
接口报 Unexpected token '<'/api 被回退成 HTML,需单独 location
部署在 S3/OSS 上,刷新即 404错误文档没指向 index.html
发版后白屏,报 module script 加载失败index.html 被缓存,引用了旧 hash 资源
回退生效了,但乱输路径返回 200 空白页缺 catch-all 404 路由

上线前自检清单

# 1) 直接访问一个深链接,期望 200 + text/html
curl -I https://site/user/123

# 2) 访问一个 /api 路径,期望 JSON 或 4xx/5xx,不是 HTML
curl -i https://site/api/xxx
  1. 检查返回的 index.html 里资源路径是否带正确的 base 前缀;
  2. 刷新、直接粘贴链接、后退,各试一次;
  3. 故意访问一个不存在的路由,确认前端 404 页出现。

模式选择的边界

面向内部、菜单驱动的后台,hash 模式省掉一整套服务端约束,成本更低;面向公开流量、需要分享链接和 SEO 的站点,history 加回退是必须付的配置成本。别在同一个域名下混用两种模式。

复制全文 生成海报 前端路由 Vue Router Nginx history模式 SPA部署

推荐文章

程序员茄子在线接单