一、引言:React Router v7 的诞生背景
1.1 发布时间与背景
2024 年 11 月 22 日 ,React Router 团队正式宣布 React Router v7 稳定版发布。这是一个具有里程碑意义的版本,标志着 React Router 从一个单纯的路由库 进化为一个现代化的全栈框架。
React Router 作为 React 生态系统中最受欢迎的路由解决方案,其 NPM 周下载量高达1000 万+。在前端开发中,几乎每一个 React 项目都离不开它的身影。
v7 版本最大的变化是将 Remix 框架的核心特性完整整合到了 React Router 中。联合创始人 Ryan Florence 宣布:"我们把 Remix 全部带回 React Router 中"。这意味着开发者无需使用 Remix,就可以直接享受到全栈开发的能力。
二、React Router v7 核心新特性
2.1 双模式架构:框架模式 vs 库模式
React Router v7 提供了三种使用模式,开发者可以根据项目需求灵活选择:
声明模式(Declarative Mode)
这是最经典的使用方式,适合简单项目和已有项目的集成:
jsx
import { BrowserRouter, Routes, Route } from "react-router";
function App() {
return (
<BrowserRouter>
<Routes>
{/* 定义路由规则:URL 路径与组件映射 */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
声明模式提供基本的路由功能:
- URL 与组件匹配
- 应用内导航
Link、useNavigate、useLocation等 API
数据模式(Data Mode)
数据模式将路由配置移到 React 渲染之外,支持 loader、action 等数据处理 API:
jsx
import { createBrowserRouter, RouterProvider } from "react-router";
// 使用配置对象定义路由,支持 loader 函数在渲染前加载数据
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
loader: loadRootData, // 根路由数据加载
children: [
{
path: "users",
element: <Users />,
loader: loadUsersData, // 子路由数据加载
},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
数据模式的优势:
- 路由级数据加载(loader)
- 数据变更处理(action)
- 挂起状态管理
- 类似 Vue Router 的配置方式
框架模式(Framework Mode)
框架模式是 v7 的重头戏,整合了 Remix 的全部特性:
jsx
// react-router.config.ts - 框架模式路由配置
import { index, route } from "@react-router/dev/routes";
export default [
index("./home.tsx"), // 首页路由:/
route("about", "./about.tsx"), // 关于页:/about
route("users/:id", "./user.tsx"), // 动态路由:/users/:id
];
框架模式特性:
- 文件即路由:基于文件系统自动生成路由
- 类型安全的 href:编译时路由检查
- 智能代码分割:自动按路由拆分代码
- SSR/SSG 支持:服务端渲染和静态站点生成
- 完整的 Remix 体验:无需单独安装 Remix
2.2 基于 Vite 的编译器
React Router v7 引入了基于 Vite 的现代化编译器,带来极致的开发体验:
bash
# 使用框架模式创建项目
npx create-react-router@latest my-react-router-app
cd my-react-router-app
npm install
npm run dev
编译器特性:
- 极速开发构建:利用 Vite 的 ESM 原生支持,毫秒级热更新
- 优化的代码分割:智能按路由拆分,只加载当前页面所需代码
- 更小的打包体积:Tree-shaking 和代码压缩优化
- HMR 热模块替换:保持应用状态的实时更新
2.3 增强的服务端渲染(SSR)支持
v7 原生支持多种渲染策略:
SSR 服务端渲染
jsx
// entry.server.tsx - 服务端入口文件
import { renderToString } from "react-dom/server";
import { createStaticRouter, StaticRouterProvider } from "react-router";
export async function handleRequest(request) {
// 创建静态路由器,传入请求的 URL
const router = createStaticRouter(routes, {
location: request.url,
});
// 将 React 组件渲染为 HTML 字符串
const html = renderToString(<StaticRouterProvider router={router} />);
// 返回 HTML 响应
return new Response(html, {
headers: { "Content-Type": "text/html" },
});
}
SSG 静态站点生成
jsx
// react-router.config.ts - 静态站点生成配置
export default {
ssr: true, // 启用服务端渲染
prerender: ["/", "/about", "/contact"], // 预渲染的路径列表
};
SSR 的优势:
- 更快的首屏加载:服务端直接返回完整 HTML
- 更好的 SEO:搜索引擎可直接抓取内容
- 渐进式增强:即使 JavaScript 失效也能显示内容
2.4 数据加载与操作
这是 React Router v7 最强大的特性之一,彻底改变了 React 应用的数据处理方式。
loader 函数:路由渲染前加载数据
loader 是在路由渲染之前执行的异步函数,用于预先获取页面所需的数据。它解决了传统 React 应用中"先渲染组件,再在 useEffect 中获取数据"导致的瀑布流问题,让数据加载和路由导航同步进行,显著提升页面加载性能。
jsx
// routes/users.tsx
export async function loader({ params, request }) {
const { userId } = params;
// 使用 Promise.all 并行获取数据,避免串行请求导致的延迟
const [user, posts] = await Promise.all([
fetch(`/api/users/${userId}`).then((r) => r.json()),
fetch(`/api/users/${userId}/posts`).then((r) => r.json()),
]);
return { user, posts };
}
export default function UserProfile() {
// 使用 useLoaderData 获取 loader 返回的数据
const { user, posts } = useLoaderData();
return (
<div>
<h1>{user.name}</h1>
<PostList posts={posts} />
</div>
);
}
clientLoader:客户端数据加载
clientLoader 是仅在客户端(浏览器)执行的数据加载函数,用于处理需要访问浏览器 API 的场景(如 localStorage、IndexedDB、浏览器缓存等)。它可以与服务端 loader 配合使用,实现混合数据加载策略。
jsx
// clientLoader:在客户端执行的数据加载函数
export async function clientLoader({ serverLoader }) {
// 调用服务端 loader 获取数据
const serverData = await serverLoader();
// 添加客户端特有的数据(如 localStorage、IndexedDB 等)
const clientData = await fetchClientOnlyData();
return { ...serverData, ...clientData };
}
action 函数:处理表单提交和数据变更
action 是处理数据变更操作(POST、PUT、DELETE 等)的服务端函数,类似于传统 Web 开发中的表单处理接口。当用户提交表单时,action 会先执行,完成后自动重新验证相关的 loader 数据,实现声明式的数据流管理。
jsx
// routes/users.tsx
export async function action({ request }) {
// 获取表单数据
const formData = await request.formData();
const name = formData.get("name");
const email = formData.get("email");
// 创建用户
const newUser = await createUser({ name, email });
// 创建成功后重定向到用户详情页
return redirect(`/users/${newUser.id}`);
}
export default function NewUser() {
return (
// Form 组件提交时会自动调用 action 函数
<Form method="post">
<input name="name" placeholder="姓名" />
<input name="email" type="email" placeholder="邮箱" />
<button type="submit">创建用户</button>
</Form>
);
}
useFetcher:非导航数据交互
useFetcher 是用于不触发页面导航的数据交互 Hook。当你需要调用 loader 或 action,但不想改变当前 URL 时使用(如实时搜索、点赞、添加购物车等场景)。它提供了完整的请求状态管理和自动竞态处理。
jsx
// useFetcher 用于不需要导航的数据交互(如搜索、点赞等)
function SearchComponent() {
const fetcher = useFetcher();
return (
<fetcher.Form method="get" action="/search">
<input
name="query"
onChange={(e) => {
// 提交表单,自动处理竞态条件(取消过期请求)
fetcher.submit(e.target.form);
}}
/>
{/* 根据 fetcher 状态显示不同 UI */}
{fetcher.state === "loading" && <Spinner />}
{fetcher.data && <SearchResults data={fetcher.data} />}
</fetcher.Form>
);
}
数据加载的优势:
- 消除数据瀑布流:数据在路由匹配时并行获取
- 自动竞态处理:取消过期的请求
- 内置加载状态:无需手动管理 loading 状态
- 自动重新验证:action 完成后自动刷新数据
2.5 类型安全增强
v7 提供了完整的 TypeScript 支持:
tsx
// 路由参数类型推断
import { useParams, useLoaderData } from "react-router";
// loader 返回类型
export async function loader({ params }: LoaderFunctionArgs) {
const user = await getUser(params.userId);
return { user };
}
// 组件中使用,获得完整的类型推断
export default function UserPage() {
const { userId } = useParams<{ userId: string }>(); // 路径参数类型
const { user } = useLoaderData<typeof loader>(); // loader 数据类型
return <div>{user.name}</div>;
}
类型安全特性:
- 路由参数自动类型推断
- loader/action 返回值类型检查
- 导航路径类型提示
- 编译时错误检查
2.6 其他新特性
View Transitions API 支持
View Transitions API 是浏览器原生提供的页面过渡动画能力,React Router v7 内置了对它的支持。通过简单的配置,即可实现类似原生应用的流畅页面切换效果,无需额外的动画库。
jsx
// 启用原生页面过渡动画(需浏览器支持 View Transitions API)
<Link to="/about" viewTransition>
关于我们
</Link>;
// 编程式导航也支持
navigate("/about", { viewTransition: true });
内置错误边界
错误边界(Error Boundary)是 React Router v7 内置的错误处理机制。当路由渲染或数据加载过程中发生错误时,会自动捕获并渲染指定的错误组件,避免整个应用崩溃。支持分层错误处理,子路由错误不会影响父路由。
jsx
// 为路由配置错误边界,捕获渲染和数据加载中的错误
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />, // 根路由错误边界
children: [
{
path: "users/:id",
element: <User />,
errorElement: <UserError />, // 子路由错误边界
},
],
},
]);
预加载与资源预取
预加载(Prefetch)是在用户实际访问页面之前,提前加载路由所需的代码和数据,从而实现"瞬间"页面切换。React Router v7 提供了多种预加载策略,可以根据用户行为(如鼠标悬停、链接进入视口等)智能预加载。
jsx
// prefetch 属性控制何时预加载路由资源
<Link to="/products" prefetch="intent">
产品列表
</Link>
// prefetch 选项说明:
// - "none":不预加载
// - "intent":鼠标悬停时预加载(推荐)
// - "render":Link 渲染时立即预加载
// - "viewport":Link 进入视口时预加载
三、React Router v7 路由配置与使用
3.1 路由模式选择
React Router v7 提供多种路由器创建函数:
| 路由器类型 | 使用场景 | 特点 |
|---|---|---|
createBrowserRouter |
Web 应用(推荐) | 使用 HTML5 History API,URL 干净 |
createHashRouter |
静态文件托管 | URL 带 # 号,无需服务器配置 |
createMemoryRouter |
测试/非浏览器环境 | 路由状态存储在内存中 |
createStaticRouter |
SSR 服务端渲染 | 服务端专用 |
jsx
// Browser 模式(推荐)- 使用 HTML5 History API
import { createBrowserRouter } from "react-router";
const router = createBrowserRouter(routes);
// Hash 模式 - URL 带 # 号,适用于静态文件部署
import { createHashRouter } from "react-router";
const router = createHashRouter(routes);
3.2 基础路由配置
方式一:配置式(推荐)
jsx
import { createBrowserRouter, RouterProvider } from "react-router";
// 使用对象数组定义路由配置,支持嵌套和错误处理
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
errorElement: <ErrorBoundary />, // 错误边界
children: [
{ index: true, element: <Home /> }, // 索引路由,匹配父路径
{ path: "about", element: <About /> },
{ path: "users", element: <Users /> },
{ path: "users/:id", element: <UserDetail /> }, // 动态参数
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
方式二:声明式(JSX)
jsx
import { BrowserRouter, Routes, Route } from "react-router";
function App() {
return (
<BrowserRouter>
<Routes>
{/* 使用 JSX 声明路由,嵌套路由通过组件嵌套实现 */}
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="users" element={<Users />} />
<Route path="users/:id" element={<UserDetail />} />
</Route>
</Routes>
</BrowserRouter>
);
}
方式三:useRoutes Hook
jsx
import { useRoutes } from "react-router";
function App() {
// 使用 Hook 方式定义路由,返回匹配的路由元素
const element = useRoutes([
{
path: "/",
element: <Layout />,
children: [
{ index: true, element: <Home /> },
{ path: "about", element: <About /> },
],
},
]);
return element;
}
3.3 嵌套路由与 Outlet
嵌套路由是 React Router 的核心概念,v7 让它变得更加简单:
jsx
// 路由配置 - 多层嵌套路由
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{ index: true, element: <Home /> },
{
path: "dashboard",
element: <DashboardLayout />,
children: [
{ index: true, element: <DashboardHome /> },
{ path: "settings", element: <Settings /> }, // /dashboard/settings
{ path: "profile", element: <Profile /> }, // /dashboard/profile
],
},
],
},
]);
// RootLayout.tsx - 使用 Outlet 渲染子路由
import { Outlet, Link } from "react-router";
function RootLayout() {
return (
<div>
<nav>
<Link to="/">首页</Link>
<Link to="/dashboard">控制台</Link>
</nav>
<main>
<Outlet /> {/* 子路由组件在此位置渲染 */}
</main>
</div>
);
}
3.4 路由导航
声明式导航:Link 与 NavLink
jsx
import { Link, NavLink } from "react-router";
function Navigation() {
return (
<nav>
{/* 基础 Link - 声明式导航 */}
<Link to="/about">关于我们</Link>
{/* 带状态的 Link - 传递额外数据 */}
<Link to="/users/1" state={{ from: "home" }}>
用户详情
</Link>
{/* NavLink - 自动为激活链接添加样式 */}
<NavLink
to="/dashboard"
className={({ isActive }) => (isActive ? "active" : "")}
style={({ isActive }) => ({
fontWeight: isActive ? "bold" : "normal",
})}
>
控制台
</NavLink>
{/* 相对路径导航 */}
<Link to="../settings">设置</Link> {/* 上一级 + settings */}
<Link to="./profile">个人资料</Link> {/* 当前级 + profile */}
</nav>
);
}
编程式导航:useNavigate
jsx
import { useNavigate } from "react-router";
function LoginForm() {
const navigate = useNavigate();
const handleLogin = async (credentials) => {
const result = await login(credentials);
if (result.success) {
// 基础导航 - 添加新历史记录
navigate("/dashboard");
// replace 导航 - 替换当前历史记录(不可后退)
navigate("/dashboard", { replace: true });
// 携带 state 数据
navigate("/dashboard", { state: { user: result.user } });
// 历史记录导航
navigate(-1); // 后退一步
navigate(1); // 前进一步
}
};
return <form onSubmit={handleLogin}>{/* 表单内容 */}</form>;
}
3.5 路由传参
URL 参数(params)
jsx
// 路由配置 - 使用 :参数名 定义动态路由
{ path: "users/:userId/posts/:postId", element: <PostDetail /> }
// 组件中获取 URL 参数
import { useParams } from "react-router";
function PostDetail() {
// 从 URL 中提取参数,如 /users/123/posts/456
const { userId, postId } = useParams();
return (
<div>
<p>用户ID: {userId}</p>
<p>文章ID: {postId}</p>
</div>
);
}
查询参数(query)
jsx
import { useSearchParams } from "react-router";
function ProductList() {
// useSearchParams 类似 useState,返回参数对象和更新函数
const [searchParams, setSearchParams] = useSearchParams();
// 获取查询参数,如 /products?page=1&category=books
const page = searchParams.get("page") || "1";
const category = searchParams.get("category");
// 更新查询参数(会触发导航)
const handlePageChange = (newPage) => {
setSearchParams({ page: newPage, category });
};
return (
<div>
<p>当前页: {page}</p>
<button onClick={() => handlePageChange(Number(page) + 1)}>下一页</button>
</div>
);
}
状态参数(state)
jsx
// 传递 state - 在导航时传递隐藏数据(不在 URL 中显示)
<Link to="/user/1" state={{ from: "list", timestamp: Date.now() }}>
查看用户
</Link>;
// 编程式导航传递 state
navigate("/user/1", { state: { from: "list" } });
// 接收 state
import { useLocation } from "react-router";
function UserDetail() {
const location = useLocation();
// 从 location.state 中获取传递的数据
const { from, timestamp } = location.state || {};
return (
<div>
<p>来源: {from}</p>
</div>
);
}
3.6 动态路由与通配符
jsx
const router = createBrowserRouter([
// 动态参数 - 使用 :参数名
{ path: "users/:id", element: <User /> },
// 可选参数 - 通过嵌套路由实现
{
path: "search",
children: [
{ index: true, element: <Search /> }, // 匹配 /search
{ path: ":query", element: <Search /> }, // 匹配 /search/keyword
],
},
// 通配符 * - 匹配该路径下的所有子路径
{ path: "docs/*", element: <Docs /> },
// 404 页面 - 必须放在最后,匹配所有未定义的路由
{ path: "*", element: <NotFound /> },
]);
3.7 404 页面与错误处理
jsx
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <RootError />, // 捕获根路由及子路由的错误
children: [
{ index: true, element: <Home /> },
{
path: "users/:id",
element: <User />,
loader: userLoader,
errorElement: <UserError />, // 捕获该路由的错误(优先级更高)
},
],
},
// 全局 404 - 捕获所有未匹配的路由
{ path: "*", element: <NotFound /> },
]);
// 错误组件 - 使用 useRouteError 获取错误信息
import { useRouteError, isRouteErrorResponse } from "react-router";
function RootError() {
const error = useRouteError();
// 判断是否为路由错误响应(如 404、500)
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>
{error.status} {error.statusText}
</h1>
<p>{error.data}</p>
</div>
);
}
// 其他类型的错误
return (
<div>
<h1>出错了!</h1>
<p>{error.message}</p>
</div>
);
}
四、全栈开发特性
4.1 SSR 服务端渲染实现
基础 SSR 配置
服务端渲染(SSR)的核心是在服务器上将 React 组件渲染为 HTML 字符串,然后发送给客户端。React Router v7 通过 createStaticHandler 和 createStaticRouter 提供了完整的 SSR 支持,可以在服务端执行路由匹配、运行 loader 函数并生成完整的 HTML 页面。
jsx
// entry.server.tsx - SSR 服务端入口
import { renderToString } from "react-dom/server";
import {
createStaticHandler,
createStaticRouter,
StaticRouterProvider,
} from "react-router";
import routes from "./routes";
export async function handleRequest(request) {
// 创建静态处理器,用于匹配路由和执行 loader
const handler = createStaticHandler(routes);
// 执行路由匹配并获取数据
const context = await handler.query(request);
// 处理重定向响应
if (context instanceof Response) {
return context;
}
// 创建静态路由器
const router = createStaticRouter(handler.dataRoutes, context);
// 渲染为 HTML 字符串
const html = renderToString(
<StaticRouterProvider router={router} context={context} />
);
return new Response(`<!DOCTYPE html>${html}`, {
headers: { "Content-Type": "text/html" },
});
}
数据注水与脱水
数据注水(Hydration)是 SSR 的关键机制。服务端渲染完 HTML 后,需要将 loader 获取的数据序列化并注入到 HTML 中;客户端接收到页面后,使用相同的数据"激活"静态 HTML,使其成为可交互的 React 应用。这个过程避免了客户端重复请求数据,提升了性能和用户体验。
jsx
// 服务端:将 loader 数据注入到 HTML 中(数据注水)
const html = `
<!DOCTYPE html>
<html>
<head><title>My App</title></head>
<body>
<div id="root">${appHtml}</div>
<script>
// 将服务端数据序列化后注入到 window 对象
window.__INITIAL_DATA__ = ${JSON.stringify(context.loaderData)};
</script>
<script src="/bundle.js"></script>
</body>
</html>
`;
// 客户端:从注入的数据恢复应用状态(数据脱水)
import { hydrateRoot } from "react-dom/client";
hydrateRoot(
document.getElementById("root"),
<RouterProvider router={router} />
);
4.2 数据预加载策略
合理的数据加载策略可以显著提升应用性能。React Router v7 提供了两种主要策略:并行加载和延迟加载。
并行数据获取
当页面需要多个数据源时,使用 Promise.all 并行请求可以大幅减少总加载时间。例如,如果 3 个请求各需 100ms,串行执行需 300ms,而并行执行只需 100ms。
jsx
// 并行数据获取 - 消除请求瀑布流,提升性能
export async function loader({ params }) {
// Promise.all 并行执行多个请求,而非串行等待
const [user, posts, comments] = await Promise.all([
fetchUser(params.userId),
fetchPosts(params.userId),
fetchComments(params.userId),
]);
return { user, posts, comments };
}
延迟数据加载(Deferred Data)
对于非关键数据,可以使用延迟加载实现流式渲染。关键数据(如用户信息)立即 await 并阻塞渲染,确保首屏完整;次要数据(如评论列表)以 Promise 形式返回,页面先渲染,数据到达后再填充。这种策略在慢速 API 场景下能显著改善首屏时间。
注意 :在 React Router v7 中,defer 函数已被弃用,推荐直接返回包含 Promise 的普通对象。
jsx
// v7 推荐方式 - 直接返回包含 Promise 的对象
import { Await } from "react-router";
import { Suspense } from "react";
export async function loader({ params }) {
// 关键数据立即 await,阻塞渲染直到获取完成
const user = await fetchUser(params.userId);
// 非关键数据不 await,返回 Promise
const postsPromise = fetchPosts(params.userId);
// 直接返回对象,无需使用 defer
return {
user,
posts: postsPromise, // 返回 Promise 而非 await 结果
};
}
export default function UserPage() {
const { user, posts } = useLoaderData();
return (
<div>
<h1>{user.name}</h1>
{/* 使用 Suspense 和 Await 处理延迟数据 */}
<Suspense fallback={<Spinner />}>
<Await resolve={posts}>
{(resolvedPosts) => <PostList posts={resolvedPosts} />}
</Await>
</Suspense>
</div>
);
}
4.3 跨环境运行能力
React Router v7 提供了强大的跨平台部署能力,通过不同的适配器可以在多种运行环境中无缝运行。这种设计使得同一套代码可以部署到不同的平台,而无需大量修改。
支持的运行环境
| 环境 | 适配器 | 特点 |
|---|---|---|
| Node.js | @react-router/node |
传统服务器环境,最灵活 |
| Cloudflare Workers | @react-router/cloudflare |
边缘计算,全球低延迟 |
| Deno | @react-router/deno |
现代 JavaScript 运行时 |
| Vercel | @vercel/remix |
Serverless 部署,开箱即用 |
| Netlify | @netlify/remix-adapter |
JAMstack 优化,CI/CD 集成 |
| EdgeOne Pages | @edgeone/react-router |
腾讯云边缘平台,国内访问友好 |
五、部署实战指南
React Router v7 支持多种部署模式,从简单的 SPA 到复杂的 SSR 全栈应用。本章将介绍不同场景下的部署最佳实践。
5.1 部署模式概览
| 渲染模式 | 构建方式 | 输出目录 | 适用场景 |
|---|---|---|---|
| SPA(客户端) | ssr: false |
build/client |
后台管理系统 |
| SSR(动态) | ssr: true |
build |
需要动态内容、SEO 优化 |
| SSG(静态) | prerender() |
build/client |
博客、文档站等 |
5.2 SPA 单页应用部署
SPA 模式是最简单的部署方式,适合纯客户端渲染的应用(如后台管理系统)。只需关闭 SSR 配置,构建后部署到任何静态服务器即可。
构建配置
typescript
// react-router.config.ts
export default {
ssr: false, // 关闭 SSR
} satisfies Config;
Nginx 配置
nginx
server {
listen 80;
server_name example.com;
root /var/www/html;
# SPA 路由支持(关键配置)
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
5.3 SSR 全栈应用部署
SSR 模式需要 Node.js 服务器运行时环境,适合需要 SEO 优化和快速首屏加载的应用。示例通过 Express 搭建服务器,使用 PM2 管理进程,保证应用稳定运行。
构建配置
typescript
// react-router.config.ts
export default {
ssr: true, // 启用 SSR(默认)
} satisfies Config;
Express 服务器
javascript
// server.js
import express from "express";
import { createRequestHandler } from "@react-router/express";
const app = express();
// 托管静态资源
app.use(express.static("build/client", { maxAge: "1y" }));
// SSR 处理
app.all(
"*",
createRequestHandler({
build: await import("./build/server/index.js"),
})
);
app.listen(3000);
PM2 进程管理
bash
# 启动应用
pm2 start server.js --name "my-app"
# 开机自启
pm2 startup && pm2 save
5.4 SSG 静态站点生成
SSG 模式在构建时预渲染所有路由为静态 HTML 文件,结合了 SSR 的 SEO 优势和 SPA 的部署简便性,特别适合内容相对固定的网站(如博客、文档站)。
构建配置
typescript
// react-router.config.ts
export default {
ssr: true,
async prerender() {
return ["/", "/about", "/contact"]; // 预渲染路由列表
},
} satisfies Config;
构建后生成静态 HTML 文件,可部署到任何静态托管平台。
5.5 EdgeOne Pages 部署
EdgeOne Pages 是腾讯云推出的边缘全栈应用平台,支持 SPA、SSR、SSG 等不同渲染模式的一键部署。采用无服务器架构,零运维成本,通过 Git 连接或 CLI 即可快速部署,平台自动处理构建和边缘分发,简单易用,特别适合需要全球低延迟访问的应用。
平台优势
- 全球边缘网络:基于 EdgeOne CDN,全球低延迟
- 自动构建部署:Git 提交后自动部署
- 零配置 SSR:无需手动配置服务器
- 国内访问优化:针对中国网络优化
部署步骤
1. 安装适配器
bash
npm install @edgeone/react-router
2. 配置 Vite
typescript
// vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import { edgeoneAdapter } from "@edgeone/react-router";
export default defineConfig({
plugins: [reactRouter(), edgeoneAdapter()],
});
3. 部署项目
方式一:Git 连接部署(推荐)
- 推送代码到 GitHub/GitLab/Gitee
- 访问 EdgeOne Pages 控制台
- 导入 Git 仓库,平台自动识别并部署
方式二:CLI 部署
bash
npm install -g @edgeone/cli
edgeone login
edgeone pages deploy
渲染模式配置
typescript
// react-router.config.ts
export default {
// SSR 模式(默认)
ssr: true,
// SSG 模式
// async prerender() { return ["/", "/about"]; },
// SPA 模式
// ssr: false,
} satisfies Config;
详细内容可参考 EdgeOne Pages 文档。
六、最佳实践与性能优化
6.1 路由设计原则
良好的路由设计是构建可维护 React 应用的基础。合理的路由层级结构不仅能提升代码可读性,还能通过共享布局组件减少重复代码,提高开发效率。
合理的路由层级
嵌套路由允许父子路由共享布局和逻辑,避免在每个页面重复导航栏、侧边栏等公共组件。推荐使用清晰的层级结构,而非过度扁平化的路由配置。
jsx
// ✅ 推荐:清晰的层级结构,便于维护和共享布局
const routes = [
{
path: "/",
element: <RootLayout />,
children: [
{ index: true, element: <Home /> },
{
path: "dashboard",
element: <DashboardLayout />, // 共享布局组件
children: [
{ index: true, element: <Overview /> },
{ path: "analytics", element: <Analytics /> },
{ path: "settings", element: <Settings /> },
],
},
],
},
];
// ❌ 避免:过度扁平化,无法共享布局,路径重复
const routes = [
{ path: "/", element: <Home /> },
{ path: "/dashboard", element: <Dashboard /> },
{ path: "/dashboard/analytics", element: <Analytics /> }, // 路径重复
{ path: "/dashboard/settings", element: <Settings /> }, // 路径重复
];
代码分割策略
代码分割是优化应用性能的重要手段。通过将不同路由的代码拆分成独立的 chunk,可以实现按需加载,显著减少首屏加载时间。React Router 配合 React.lazy 可以轻松实现路由级代码分割。
jsx
import { lazy, Suspense } from "react";
// 使用 React.lazy 实现路由级代码分割
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{
path: "dashboard",
element: (
// Suspense 处理加载状态
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
),
},
{
path: "settings",
element: (
<Suspense fallback={<Loading />}>
<Settings />
</Suspense>
),
},
],
},
]);
6.2 数据加载优化
高效的数据加载策略能够大幅提升用户体验。通过并行请求、合理的缓存策略,可以减少页面加载时间,避免不必要的网络请求。
并行数据请求
当页面需要多个数据源时,串行请求会导致"请求瀑布流"问题,总耗时是所有请求时间的总和。使用 Promise.all 并行请求可以让多个请求同时进行,总耗时仅为最慢请求的时间。
jsx
export async function loader({ params }) {
// ✅ 推荐:并行请求,减少总耗时
const [user, posts, comments] = await Promise.all([
fetchUser(params.id),
fetchPosts(params.id),
fetchComments(params.id),
]);
return { user, posts, comments };
}
缓存策略
合理的缓存策略可以避免重复加载相同数据,提升应用性能。React Router 提供了 shouldRevalidate 函数,允许你精确控制何时重新执行 loader,何时使用缓存数据。
jsx
// 使用 shouldRevalidate 控制何时重新执行 loader
export function shouldRevalidate({ currentUrl, nextUrl, formMethod }) {
// 只在 POST/PUT/DELETE 等数据变更操作后重新验证
if (formMethod && formMethod !== "GET") {
return true;
}
// 查询参数变化时重新验证
if (currentUrl.search !== nextUrl.search) {
return true;
}
// 其他情况使用缓存数据
return false;
}
6.3 路由守卫实现
路由守卫用于控制页面访问权限,是实现认证和授权的常用手段。React Router v7 提供了两种实现方式:使用 loader 函数(推荐)或封装高阶组件。
jsx
// 方式 1:使用 loader 实现认证守卫(推荐)
{
path: "dashboard",
element: <Dashboard />,
loader: async ({ request }) => {
const session = await getSession(request);
if (!session) {
// 保存原始 URL,登录后可跳转回来
const url = new URL(request.url);
throw redirect(`/login?redirectTo=${url.pathname}`);
}
return { user: session.user };
},
}
// 方式 2:封装 ProtectedRoute 组件
function ProtectedRoute({ children }) {
const { user, loading } = useAuth();
const location = useLocation();
if (loading) return <Loading />;
if (!user) {
// 未登录则重定向到登录页,并保存当前位置
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 使用方式
{
path: "dashboard",
element: (
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
),
}
6.4 TypeScript 类型定义
TypeScript 可以为路由系统提供完整的类型安全保障,避免参数错误、数据类型不匹配等常见问题。React Router v7 提供了完善的类型支持,结合 TypeScript 可以获得更好的开发体验。
typescript
// types/router.ts
import type { LoaderFunctionArgs, ActionFunctionArgs } from "react-router";
// 定义 loader 返回类型
interface UserLoaderData {
user: User;
posts: Post[];
}
// loader 函数带类型注解
export async function loader({
params,
}: LoaderFunctionArgs): Promise<UserLoaderData> {
const user = await fetchUser(params.userId!);
const posts = await fetchPosts(params.userId!);
return { user, posts };
}
// 组件中类型安全地使用数据
import { useLoaderData } from "react-router";
function UserPage() {
// 方式 1:手动指定类型
const { user, posts } = useLoaderData() as UserLoaderData;
// 方式 2:使用 typeof 自动推断(推荐)
// const data = useLoaderData<typeof loader>();
return (
<div>
<h1>{user.name}</h1>
<PostList posts={posts} />
</div>
);
}
七、总结与展望
7.1 v7 核心价值
React Router v7 的发布标志着一个重要的里程碑:
- 路由库到全栈框架的进化:不再只是 URL 匹配工具,而是完整的应用架构解决方案
- Remix 能力的民主化:无需学习新框架,现有 React Router 用户即可享受全栈特性
- 开发体验的飞跃:类型安全、HMR、自动代码分割、数据加载机制
7.2 适用场景建议
| 场景 | 推荐模式 |
|---|---|
| 简单 SPA | 声明模式 |
| 现有项目集成 | 数据模式 |
| 新项目(需要 SSR) | 框架模式 |
| 全栈应用 | 框架模式 |
| 静态站点 | 框架模式 + SSG |
7.3 与 Next.js 的对比
| 特性 | React Router v7 | Next.js |
|---|---|---|
| 路由方式 | 配置式/文件式 | 文件式 |
| SSR 支持 | ✅ | ✅ |
| SSG 支持 | ✅ | ✅ |
| 数据获取 | loader/action | getServerSideProps 等 |
| 学习曲线 | 低(基于 React Router) | 中等 |
| 生态系统 | React 生态 | Vercel 生态 |
| 适用场景 | 交互密集型应用 | 内容驱动型网站 |
值得一提的是,OpenAI 官网已从 Next.js 切换到 Remix/React Router,证明了其在复杂应用中的实力。
7.4 未来发展方向
React Router 团队已经透露了 v7 的后续计划:
- React Server Components(RSC)支持:即将推出
- 更多 Remix 特性整合:持续优化
- 性能优化:更小的包体积、更快的运行时
- 开发工具:更好的 DevTools 支持
参考资源
写在最后:React Router v7 的发布是 React 生态系统的一次重大进步。无论你是 React Router 的老用户还是新手,v7 都值得你认真学习和使用。它不仅简化了路由管理,更提供了构建现代化全栈应用的完整解决方案。现在就开始你的 v7 之旅吧!