react路由懒加载、history 模式刷新404 解决方案

这是 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:避免过度拆分导致请求数量过多,根据业务模块进行代码分割。这样既能提升首屏性能,又能兼顾后续页面切换体验。