单页应用刷新404是因服务器将前端路由误作真实文件路径查找;需配置fallback机制(如Nginx用try_files $uri $uri/ /index.html),确保非资源请求均返回index.html且状态码为200,同时排除API路径。

刷新页面变404,基本不是HTML文件本身的问题,而是服务器没找到对应路径——尤其在单页应用(SPA)里,/user/123 这种前端路由根本没对应的真实文件,不配 fallback 就必然 404。
为什么本地双击打开没事,一上服务器就404
双击 index.html 是走 file:// 协议,浏览器不发请求给服务器,所有路由都由 JS 自己处理;而部署到服务器后,每次刷新都是向服务端发起新请求,比如访问 https://site.com/about,服务器会按路径去磁盘找 /about/index.html 或 /about.html ——SPA 根本没生成这些文件,自然返回 404。
- 本地用
vite preview或http-server -s不报错,是因为它们默认启用 SPA fallback 模式 - 裸跑
http-server(无-s)、Nginx 默认配置、GitHub Pages、甚至部分 Vercel 部署,都不自动 fallback - Netlify 必须显式写
[[redirects]]规则,且status = 200才是 rewrite,status = 301是跳转,会破坏路由
Nginx 的 try_files 必须这么写
error_page 404 /index.html 是错的:它会让 URL 变成 /index.html,JS 里 location.pathname 拿到的是 /index.html 而不是原始路径,前端路由直接失效。
- 正确写法是:
location / { try_files $uri $uri/ /index.html; } -
$uri先查真实文件(如/logo.png)→ 存在就返回 -
$uri/再查目录(如/assets/)→ 存在就尝试找该目录下index.html - 都失败才 fallback 到
/index.html,且 HTTP 状态码仍是 200,URL 不变 - 别漏掉
root或alias配置,否则/index.html文件本身找不到
Netlify 和 Vercel 的重写陷阱
Netlify 的 _redirects 文件不支持 status = 200 的内部重写,只支持跳转;必须用 netlify.toml:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
[[redirects]] from = "/*" to = "/index.html" status = 200
Vercel 则靠 vercel.json 或 vercel.toml 的 rewrites:
[{ "source": "/(.*)", "destination": "/" }]
- 这两者都要求把 API 路径排除在外,否则
/api/users也会被 fallback 到index.html,导致接口失效 - 推荐加前置规则,例如 Netlify:
from = "/api/*"→ 不重写;再写通配规则 - 检查部署后的实际响应头:用浏览器 DevTools 的 Network 面板确认状态码真是 200,而不是 301 或 404
别让 404 页面自己触发更多 404
自定义 404 页面如果引用了外部资源,比如 <link rel="stylesheet" href="/css/main.css">,而这个 CSS 文件本身也 404,就会连锁失败,最终白屏或样式错乱。
- 404 页面的所有样式必须内联在
<style>标签里,禁用外部 CSS/JS 请求 - 图片优先用内联 SVG 或 data URI,避免
<img src="/404-icon.png"> - 确保该页面返回的 HTTP 状态码真的是
404,不是页面写了“404”但响应头是200 OK—— 后者是“软 404”,对 SEO 和监控极不友好 - 静态托管平台(如 GitHub Pages)要求 404 页面必须叫
/404.html且放在根目录,多一个子目录都不行
最常被忽略的一点:fallback 配置生效的前提是,index.html 本身能被服务器正常读取。如果 root 路径设错、文件权限为 600、或者 SELinux 限制了访问,那连 fallback 都进不去,直接卡在第一步。


















