为什么刷新页面就 404?聊聊 SPA 路由与 Nginx 的那点事

你有没有遇到过这种情况:进入首页一切正常,JS 跳转到 /login 也没问题,但一旦在 /login 页面按下刷新,迎接你的就是一个冷冰冰的 404 Not Found

这个问题困扰过无数刚接触前后端分离项目的开发者。今天我们就来彻底搞清楚它背后的原理,以及怎么一行配置解决它。


问题复现

场景很简单:

  • 访问 http://your-ip/ → 正常,看到首页
  • JS 判断未登录,自动跳转到 /login → 正常,看到登录页
  • /login 页面按下 F5 刷新 → ❌ 404 Not Found

看起来很矛盾------明明刚才还在这个页面,刷新一下就没了?


根本原因:两种"跳转"本质不同

要理解这个问题,需要区分两件事:前端路由跳转浏览器发起 HTTP 请求

情况一:JS 跳转到 /login

arduino 复制代码
浏览器请求 http://your-ip/
       ↓
Nginx 找到 index.html,返回给浏览器 ✅
       ↓
浏览器加载并执行 JS(Vue Router / React Router)
       ↓
Router 判断:用户未登录 → 执行 router.push('/login')
       ↓
浏览器地址栏变成 http://your-ip/login

关键点: router.push('/login') 这一步,浏览器只是修改了地址栏的显示,页面内容由 JS 负责切换渲染。整个过程 Nginx 没有收到任何新的请求,它完全不知道发生了什么。

这就是现代 SPA 框架(Vue、React、Angular)的核心机制------客户端路由(Client-Side Routing)


情况二:在 /login 刷新 ❌

bash 复制代码
浏览器请求 http://your-ip/login
       ↓
Nginx 去服务器磁盘查找 /var/www/html/login 文件或目录
       ↓
找不到该文件 → 返回 404

关键点: 刷新(或直接在地址栏输入 URL 并回车)会触发浏览器向服务器发起真实的 HTTP 请求。此时 Nginx 需要在磁盘上找到对应的资源文件。

/login 根本不是一个真实存在的文件------它只是前端 JS 定义的一个路由规则。Nginx 当然找不到,于是返回 404。


一张图说清楚

markdown 复制代码
JS 跳转(前端路由)         刷新 / 直接输入 URL
─────────────────          ──────────────────────
  浏览器内部处理               发起真实 HTTP 请求
  Nginx 不感知                Nginx 必须响应
  JS 渲染新页面                磁盘无文件 → 404
       ✅                           ❌

解决方案:try_files 兜底

既然问题是 Nginx 找不到文件,那解决思路就是:让 Nginx 在找不到文件时,统一返回 index.html,把路由控制权交还给前端 JS。

修改 Nginx 配置:

bash 复制代码
server {
    listen 80;
    root /var/www/html;   # 前端构建产物目录(dist/)
    index index.html;

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

try_files 执行逻辑

bash 复制代码
try_files $uri $uri/ /index.html
    │        │           │
    │        │           └─ 都找不到?返回 index.html(兜底)
    │        └─────────────  找同名目录?
    └──────────────────────  找同名文件?

以请求 /login 为例:

  1. /var/www/html/login 文件 → 不存在
  2. /var/www/html/login/ 目录 → 不存在
  3. 返回 /var/www/html/index.html

浏览器拿到 index.html,加载 JS,前端 Router 读取当前 URL /login,渲染对应页面。完美。


配置生效

修改完 Nginx 配置后,验证并热重载:

bash 复制代码
# 验证配置语法
nginx -t

# 热重载(不中断服务)
nginx -s reload

扩展:部署在子路径下怎么办?

如果你的应用不是部署在根路径,而是 http://your-ip/app/,配置需要对应调整:

bash 复制代码
location /app {
    try_files $uri $uri/ /app/index.html;
}

同时前端框架的路由 base 也要配置一致,以 Vue Router 为例:

php 复制代码
const router = createRouter({
  history: createWebHistory('/app/'),
  routes: [...]
})

为什么 Hash 路由没有这个问题?

你可能见过 URL 长这样:http://your-ip/#/login

Hash(#)后面的内容浏览器不会发给服务器 ,服务器只会收到 http://your-ip/ 的请求,永远能找到 index.html,自然不会 404。

这也是为什么老项目或对 SEO 要求不高的后台系统喜欢用 Hash 模式------部署更简单,无需配置服务器。

模式 URL 样式 刷新 404 问题 SEO 友好
History 模式 /login 需要服务器配置
Hash 模式 /#/login 无需配置

总结

JS 路由跳转 浏览器刷新 / 直接访问
请求发起方 前端 JS(浏览器内部) 浏览器 → Nginx
Nginx 是否感知 ❌ 不感知 ✅ 感知
结果 正常渲染 找不到文件 → 404

一句话记住: SPA 的路由是"假"的,只存在于浏览器内存中。一旦刷新,浏览器就会向服务器"认真"地发起请求,而服务器并不知道这些"假"路由的存在。try_files ... /index.html 就是告诉 Nginx:"你不认识的路径,都把 index.html 给他,让前端自己搞定。"

相关推荐
不好听6133 小时前
React 核心概念:JSX、组件与数据驱动
前端·react.js
触底反弹3 小时前
🔥 React 零基础入门(中):500 行屎山代码到组件化的蜕变
前端·javascript·react.js
不好听6133 小时前
React vs Vue:两大前端框架技术选型深度对比
前端·vue.js·react.js
GuWenyue3 小时前
Cursor黑盒拆解!1套LangChain.js手写Mini编程Agent,自动生成React项目,效率提升60%
前端·数据库·人工智能
GuWenyue3 小时前
传统Agent工具两大痛点!300行代码落地MCP跨语言工具,彻底解耦LLM与工具
前端·人工智能·算法
小林ixn4 小时前
从 onclick 到 React 合成事件,再到完整应用逻辑:一次前端架构的深度剖析
前端·react.js·前端框架
Csvn4 小时前
🧩「找不到模块」排查全记录——Monorepo 下 TypeScript 路径别名的 5 种「不通」与根治方案
前端
腻害兔4 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:字典、短信、邮件、通知——后台系统的“基础设施四件套“!
java·前端·vue.js·产品经理·ai编程
CodexDave5 小时前
MySQL事务隔离级别与MVCC机制解析
前端·数据库·mysql·nginx·性能优化·负载均衡