首屏能进、刷新就白屏,问题一定不在前端路由库,而在服务器不认识这条路。
history 模式下 /order/123 是前端路由,服务器磁盘上并没有 order/123/index.html 这个文件。客户端跳转不经过服务器,所以一切正常;一旦刷新或直接粘贴链接,浏览器会向服务器要 /order/123,Nginx 找不到文件就返回 404。
hash 模式(/#/order/123)不会有这个问题,因为 # 后面的内容根本不会发给服务器——这也是很多老项目坚持用 hash 的真实原因。
server {
# ... 其他配置 ...
location / {
try_files $uri $uri/ /index.html;
}
# 项目部署在子目录时,兜底文件也要带子目录
# location /admin/ {
# try_files $uri $uri/ /admin/index.html;
# }
}
try_files 依次尝试三件事:$uri(原路径对应的文件)→ $uri/(当作目录找其下的 index)→ /index.html(内部重定向到首页,返回 200)。前端路由拿到 URL 后自己渲染对应页面。
如果 location / 的兜底写在前面,且没有更精确的匹配,JS/CSS/图片不存在时也会返回 index.html,浏览器收到 Content-Type: text/html 的「JS 文件」直接报语法错误。表现是控制台出现 Uncaught SyntaxError: Unexpected token '<'。
标准的写法是用前缀匹配把静态资源单独放出来,^~ 让它不被正则 location 抢走:
# 静态资源:命中就返回,命中不了就真 404,不要兜底到 index.html
location ^~ /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|woff2?|ttf|ico)$ {
expires 30d;
try_files $uri =404;
}
# 其余路径交给前端路由
location / {
try_files $uri $uri/ /index.html;
}
try_files $uri =404 里的 =404 是关键:静态资源不存在就明确返回 404,而不是吐一个 HTML 出去。
兜底依赖 index.html 是最新版本。如果它被缓存了,用户拿到的还是旧 HTML,里面引用的旧 hash 文件名(app.a1b2c3.js)早被新构建删除,一刷新就是全屏白板 + 一堆 404。
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
expires -1;
}
带 hash 的静态资源可以放心长缓存,index.html 必须每次回源——这是缓存策略里唯一不能反的组合。
项目访问地址是 https://example.com/admin/ 时,不只是 Nginx 要改:
| 位置 | 要改什么 | 漏改的表现 |
|---|---|---|
| Nginx | location /admin/ { try_files ... /admin/index.html; } | 刷新 404 |
| 构建配置 | base: '/admin/'(vite)或 (webpack) |
publicPath白屏,JS 请求路径变成 /assets/... 而不是 /admin/assets/... |
| 前端路由 | createBrowserRouter(..., { basename: '/admin' }) | 首页能显示,其他路由匹配不上 |
location /admin/ {
root /var/www; # 实际找 /var/www/admin/index.html
}
location /admin/ {
alias /var/www/admin/; # 实际找 /var/www/admin/index.html
}
root 是拼在路径后面,alias 是直接替换。alias 结尾必须带斜杠,否则路径会少一层;alias 里也不能直接写 try_files $uri(会把 alias 路径再拼一次),需要写成 try_files $uri $uri/ /admin/index.html 并配合 location。
很多人以为 try_files 的第一项要写文件全名。$uri 就是请求路径,Nginx 会自动拼 root/alias,不需要手动加 /index.html。
很多人以为 404 是前端路由配错了。判断方法很简单:在页面里用前端路由跳转能打开,直接访问 URL 打不开——那就一定是服务端问题,跟 react-router 版本、basename 无关。
很多人以为配置了 try_files 就万事大吉。如果前面还有一条 location /api/ 并且它也有兜底,或者 index 指令指向了别的文件,/ 的兜底可能被更精确的 location 截走。
curl -I https://example.com/order/123 # 期望 200,Content-Type: text/html
curl -I https://example.com/assets/app.js # 期望 200,Content-Type: application/javascript
curl -I https://example.com/assets/nope.js # 期望 404,不能是 200 text/html