这是React 面试中出现频率最高的问题之一,也是企业项目部署时必须解决的问题。
我会从懒加载原理 → 配置 → history刷新404 → 不同服务器解决方案 → 面试回答全面讲解。
一、React 路由懒加载(Lazy Load)
React 默认所有页面都会打包到一个 JS 中。
例如:
src ├── pages │ ├── Home │ ├── Login │ ├── User │ └── Setting正常打包:
bundle.js ↓ 包含: Home Login User Setting第一次访问:
index.html ↓ bundle.js(3M) ↓ 全部下载即使用户只访问首页,也要下载所有页面。
所以需要:
按需加载(Code Split)
React.lazy()
React 官方提供:
import { lazy } from "react"; const Home = lazy(() => import("./pages/Home")); const Login = lazy(() => import("./pages/Login"));实际上:
首页 ↓ Home.chunk.js 登录 ↓ Login.chunk.js 用户 ↓ User.chunk.js浏览器只下载当前页面。
例如:
访问
/只下载:
main.js + Home.chunk.js访问
/login再下载:
Login.chunk.js二、Suspense
因为 JS 是异步加载。
React 不知道什么时候加载完成。
所以:
import { Suspense } from "react"; <Suspense fallback={<div>Loading...</div>}> <Routes> ... </Routes> </Suspense>完整写法:
import { BrowserRouter, Routes, Route } from "react-router-dom"; import { lazy, Suspense } from "react"; const Home = lazy(() => import("./pages/Home")); const Login = lazy(() => import("./pages/Login")); export default function App() { return ( <BrowserRouter> <Suspense fallback={<div>加载中...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/login" element={<Login />} /> </Routes> </Suspense> </BrowserRouter> ); }三、为什么懒加载能减少包大小?
正常:
main.js ↓ Home Login User Order Admin ...打包:
4MB懒加载:
main.js 200KB ↓ Home.chunk.js 100KB ↓ Login.chunk.js 80KB ↓ User.chunk.js 90KB首次:
200KB后续:
按需下载这就是:
Code Splitting(代码分割)
Webpack/Vite 都支持。
四、history 模式为什么刷新404?
React Router:
BrowserRouter采用:
history APIURL:
localhost:3000/login第一次:
index.html ↓ React ↓ 识别: /login ↓ 渲染 Login 页面没有问题。
但是刷新:
F5浏览器:
GET /login请求:
服务器服务器:
寻找: /login实际上:
没有 login.html于是:
404React 根本没机会运行。
流程如下:
刷新 ↓ 浏览器 ↓ GET /login ↓ Nginx ↓ 找 login 文件 ↓ 不存在 ↓ 404五、为什么 HashRouter 不会?
Hash:
http://localhost:3000/#/login服务器收到:
GET /因为:
# 后面的内容 不会发送给服务器所以:
服务器始终返回:
index.htmlReact:
读取: #/login ↓ 跳转因此:
Hash 不会刷新404。
六、history 模式解决方案
核心思想:
无论访问什么路径,都返回 index.html,由 React 接管路由。
方法一:Nginx(生产环境最常用)
配置:
location / { try_files $uri $uri/ /index.html; }意思:
访问: /login ↓ 有没有 login? ↓ 没有 ↓ 返回 index.htmlReact:
BrowserRouter ↓ 匹配 /login ↓ Login 页面这是生产环境最推荐的方案。
方法二:Apache
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ index.html [QSA,L] </IfModule>方法三:Express(Node)
app.use(express.static("build")); app.get("*", (req, res) => { res.sendFile(path.join(__dirname, "build/index.html")); });方法四:Vite 开发环境
开发:
npm run devVite 已经帮你处理了。
刷新:
localhost:5173/login不会404。
方法五:Create React App
开发服务器:
npm start也是自动处理。
只有部署以后:
Nginx Apache Tomcat IIS需要配置。
七、React Router v6 懒加载最佳实践
可以将路由单独维护:
import { lazy } from "react"; const Home = lazy(() => import("../pages/Home")); const User = lazy(() => import("../pages/User")); export default [ { path: "/", element: <Home />, }, { path: "/user", element: <User />, }, ];App:
<Suspense fallback={<Loading />}> <Routes> {routes.map(item => ( <Route key={item.path} path={item.path} element={item.element} /> ))} </Routes> </Suspense>这样更适合大型项目维护。
八、history 与 hash 对比
| 对比项 | BrowserRouter(History) | HashRouter |
|---|---|---|
| URL | /user | #/user |
| 是否美观 | ✅ | ❌ |
| SEO | ✅ 更友好 | ❌ 较差 |
| 刷新 | 需要服务器配置 | 不需要 |
| 服务端支持 | 必须 | 不需要 |
| 推荐 | ✅ 企业项目首选 | 适合静态托管或无需服务端配置的场景 |
九、面试高频回答
如果面试官问:
React history 模式为什么刷新会 404?如何解决?
可以回答:
BrowserRouter使用的是 HTML5 History API,页面刷新时浏览器会向服务器请求当前路径(例如/user)。如果服务器上不存在对应的静态资源,就会返回 404,而 React 应用还没有机会接管路由。解决方法是在服务器配置回退规则(Fallback),例如 Nginx 使用try_files $uri $uri/ /index.html;,Express 使用app.get("*", ...)返回index.html。这样所有前端路由都会先加载入口文件,再由 React Router 根据 URL 渲染对应页面。
十、企业项目常见优化
大型 React 项目通常会结合以下方案进一步优化:
路由级懒加载:使用
React.lazy()+Suspense,按页面拆分代码,减少首屏资源。预加载(Prefetch/Preload):对于用户大概率访问的页面,可利用构建工具的预加载能力提前下载资源,提高切换速度。
加载状态优化:为
Suspense提供骨架屏(Skeleton)或 Loading 组件,避免页面空白。错误边界(Error Boundary):处理懒加载模块加载失败(如网络异常)时的降级展示,提高应用稳定性。
合理拆分 Chunk:避免过度拆分导致请求数量过多,根据业务模块进行代码分割。这样既能提升首屏性能,又能兼顾后续页面切换体验。