React Router v7 全栈开发指南: 从新特性到部署实战

一、引言: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 与组件匹配
  • 应用内导航
  • LinkuseNavigateuseLocation 等 API

数据模式(Data Mode)

数据模式将路由配置移到 React 渲染之外,支持 loaderaction 等数据处理 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。当你需要调用 loaderaction,但不想改变当前 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 路由导航

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 通过 createStaticHandlercreateStaticRouter 提供了完整的 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 连接部署(推荐)

  1. 推送代码到 GitHub/GitLab/Gitee
  2. 访问 EdgeOne Pages 控制台
  3. 导入 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 的发布标志着一个重要的里程碑:

  1. 路由库到全栈框架的进化:不再只是 URL 匹配工具,而是完整的应用架构解决方案
  2. Remix 能力的民主化:无需学习新框架,现有 React Router 用户即可享受全栈特性
  3. 开发体验的飞跃:类型安全、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 的后续计划:

  1. React Server Components(RSC)支持:即将推出
  2. 更多 Remix 特性整合:持续优化
  3. 性能优化:更小的包体积、更快的运行时
  4. 开发工具:更好的 DevTools 支持

参考资源


写在最后:React Router v7 的发布是 React 生态系统的一次重大进步。无论你是 React Router 的老用户还是新手,v7 都值得你认真学习和使用。它不仅简化了路由管理,更提供了构建现代化全栈应用的完整解决方案。现在就开始你的 v7 之旅吧!

相关推荐
Mintopia4 小时前
无界微前端:父子应用通信、路由与状态管理最佳实践
架构·前端框架·全栈
涔溪4 小时前
实现将 Vue3 项目作为子应用,通过无界(Wujie)微前端框架接入到 Vue2 主应用中(Vue2 为主应用,Vue3 为子应用)
vue.js·前端框架·wujie
4***14905 小时前
TypeScript在React中的前端框架
react.js·typescript·前端框架
S***42805 小时前
Web3.0在去中心化应用中的前端框架
前端框架·web3·去中心化
q***d17315 小时前
React桌面应用开发
前端·react.js·前端框架
Q***K5515 小时前
React高级
前端·react.js·前端框架
向下的大树20 小时前
使用 qiankun 微前端框架的心得与实践
前端框架
y***54881 天前
React依赖
前端·react.js·前端框架
2***B4491 天前
React测试
前端·react.js·前端框架