刷新React Router应用出现404,根本原因不在于前端路由配置错误,而是服务端没有把未知路径交还给单页入口文件。很多项目在开发环境用webpack-dev-server或Vite时一切正常,一旦打包部署到Nginx,直接访问深层页面或者按F5刷新立即白屏并显示404。这个问题与BrowserRouter的工作机制密切相关,解决起来也不算复杂,核心就是调整Nginx的请求回退策略。

History模式为什么刷新就404
React Router提供了两种路由模式:HashRouter和BrowserRouter。HashRouter把路径放在URL的井号后面,例如ipipp.com/#/user/123,井号之后的内容永远不会发送给服务器,因此刷新时Nginx仍然只请求根路径,不会出现404。但HashRouter的URL不够美观,也不利于SEO,所以生产环境通常选用BrowserRouter。
BrowserRouter基于History API实现地址栏变化。通过history.pushState或history.replaceState修改URL时,浏览器不会向服务器发起请求,页面内容由前端JavaScript动态渲染。然而刷新页面或直接复制链接打开时,浏览器会按照当前完整URL向服务器发送GET请求。Nginx收到/user/123这样的请求后,会去站点根目录下查找名为user/123的文件或目录,找不到就返回404。对于纯前端单页应用来说,服务器上根本没有这些路径对应的实体文件,所有路由都应该由index.html承载。
下面是一个标准的BrowserRouter使用示例,可以看到前端路由定义是完整的,刷新404并不是这段代码的问题:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import UserProfile from './pages/UserProfile';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user/:id" element={<UserProfile />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
前端已经处理了未知路径的兜底页面,但前提是浏览器请求必须能到达index.html。如果Nginx直接返回404,React代码根本没有机会执行,这就是服务端需要配合的原因。
Nginx try_files核心配置
解决刷新404最有效的方法是使用Nginx的try_files指令。它按顺序检查一组文件或目录是否存在,如果前面的都没找到,就执行最后一个参数。对于React单页应用,典型配置是把所有未命中的请求内部重定向到/index.html,注意这里不会改变浏览器地址栏,属于Nginx内部转发。
server {
listen 80;
server_name ipipp.com;
root /var/www/react-app;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
这段配置中$uri表示当前请求路径,例如/user/123;$uri/表示尝试查找对应目录;最后/index.html是内部重定向目标。Nginx会先检查站点根目录下有没有/user/123这个文件,然后检查有没有/user/123/这个目录,如果都不存在,就把请求内部转发给/index.html。React应用启动后,前端路由会根据当前URL匹配对应的组件并渲染,用户就能看到正确页面。
很多人在配置时习惯用rewrite把所有请求重写到/index.html,虽然也能实现类似效果,但try_files的语义更清晰,性能也更好。两者的关键区别在于try_files优先让真实存在的静态文件直接返回,只有文件不存在时才回退,这样不会干扰CSS、JavaScript、图片等资源的正常加载。而使用rewrite若规则不够精细,容易把静态资源也重写掉,导致MIME类型错误或资源加载失败。
另一个常见问题是配置了try_files后,访问不存在的API路径会返回index.html的内容,HTTP状态码还是200。这个问题需要结合下面的子路径和代理配置一起处理,把前端页面回退与后端接口代理拆分开。
子路径部署与API代理共存
如果React应用部署在子路径下,比如https://ipipp.com/admin/,那么Nginx配置需要针对该子路径单独处理。前端代码里也需要设置BrowserRouter的basename属性,确保路由跳转与真实路径一致。假设构建产物放在/var/www/admin/目录,对应配置如下:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter basename="/admin">
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</BrowserRouter>
);
}
Nginx配置中需要使用location /admin/来限定作用范围,并且try_files的最终回退地址要写成完整子路径/admin/index.html,否则会错误地回退到根路径的index.html,导致白屏或路由混乱。
server {
listen 80;
server_name ipipp.com;
root /var/www;
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /admin/ {
try_files $uri $uri/ /admin/index.html;
}
}
这个示例中,/api/开头的请求会被代理到后端服务,不会进入try_files逻辑。而/admin/开头的路径如果未匹配到实际文件或目录,会回退到/admin/index.html。由于root /var/www,实际文件位置是/var/www/admin/index.html,与前端构建产物的输出目录保持一致。
注意Nginx的location匹配优先级:前缀匹配中较长的location会优先于较短的location。如果同时存在location /和location /admin/,访问/admin/user会优先命中/admin/,这是符合预期的。但如果有正则location覆盖了/admin/路径,需要留意顺序,避免前端页面回退被正则规则截获。建议先保证静态资源location和API代理location准确,再把回退逻辑放在对应的路径location中。
保留真实404与静态资源缓存策略
把所有未命中请求都回退到index.html有一个副作用:当静态资源文件缺失时,Nginx也会返回index.html,浏览器拿到HTML内容去解析CSS或JavaScript时就会报错,而且HTTP状态码还是200,不利于排查问题。更合理的做法是为静态资源单独配置try_files $uri =404;,这样缺失的JS、CSS、图片等资源会真正返回404,而不是回退到应用入口。
location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
这个正则location会匹配所有常见的静态资源扩展名,为它们设置一年的浏览器缓存,并加上immutable标记,告诉浏览器资源内容不变时可以放心使用缓存。当文件不存在时,try_files $uri =404会直接返回404状态码,避免把HTML内容当作JavaScript返回。唯一需要注意的是,这个正则location会覆盖前缀location,因此如果API路径恰好以这些扩展名结尾,可能需要调整正则或把API代理配置放在更优先的位置。
对于真正的404页面,前端可以在路由表中配置<Route path="*" element={<NotFound />} />,用户访问不存在的路径时能看到友好的提示页面。但HTTP状态码仍然是200,这是单页应用无法避免的架构特点。如果业务要求API路径返回真实的HTTP 404,需要由后端接口或Nginx代理规则来保证,前端页面回退只负责用户界面导航,不负责API状态码。
排查刷新404问题时,可以先用curl -I https://ipipp.com/user/123查看响应头。如果返回200且内容类型是text/html,说明回退配置已经生效;如果返回404,说明try_files可能没有放在正确的location中,或者站点root路径配置有误。再检查静态资源请求,例如curl -I https://ipipp.com/assets/main.js,确认返回200且Content-Type为正确的JavaScript MIME类型。如果静态资源也返回了HTML,就要检查静态资源location是否被更广泛的前端回退规则覆盖。
NginxReact Router404错误修改时间:2026-09-22 00:35:26