这是 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 API
URL:
localhost:3000/login
第一次:
index.html
↓
React
↓
识别:
/login
↓
渲染 Login 页面
没有问题。
但是刷新:
F5
浏览器:
GET
/login
请求:
服务器
服务器:
寻找:
/login
实际上:
没有 login.html
于是:
404
React 根本没机会运行。
流程如下:
刷新
↓
浏览器
↓
GET /login
↓
Nginx
↓
找 login 文件
↓
不存在
↓
404
五、为什么 HashRouter 不会?
Hash:
http://localhost:3000/#/login
服务器收到:
GET /
因为:
#
后面的内容
不会发送给服务器
所以:
服务器始终返回:
index.html
React:
读取:
#/login
↓
跳转
因此:
Hash 不会刷新404。
六、history 模式解决方案
核心思想:
无论访问什么路径,都返回 index.html,由 React 接管路由。
方法一:Nginx(生产环境最常用)
配置:
location / {
try_files $uri $uri/ /index.html;
}
意思:
访问:
/login
↓
有没有 login?
↓
没有
↓
返回
index.html
React:
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 dev
Vite 已经帮你处理了。
刷新:
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:避免过度拆分导致请求数量过多,根据业务模块进行代码分割。这样既能提升首屏性能,又能兼顾后续页面切换体验。