首页
/ React Router Framework Mode 全解析:Route Modules、routes.ts 配置与渲染策略实战

React Router Framework Mode 全解析:Route Modules、routes.ts 配置与渲染策略实战

2026-09-05 10:29:26作者:尤辰城Agatha

Framework Mode 是 React Router 的全栈模式:它基于 React Router 官方 Vite 插件,以 app/routes.ts 路由配置、路由模块(Route Modules)、自动生成的路由类型为骨架,并支持 SSR、SPA、预渲染等渲染策略。本文以仓库中的 Framework Mode 参考文档为主线,结合 playground/framework 真实示例应用与 @react-router/dev 包源码,完整覆盖其工程结构、路由配置方式、路由模块全部导出项、数据与变更(mutation)规则、表单/Fetcher 使用策略、类型安全、元数据、中间件与 RSC 扩展,帮助你从"识别一个 Framework 应用"到"正确修改它"形成完整能力。

Framework Mode 是什么:三种使用模式中的全栈选项

React Router 提供三种使用模式(Mode),特性逐层叠加——从 Declarative 到 Data 再到 Framework,获得的能力越多,架构上让渡的控制权也越多。选择哪种模式取决于"你想自己搭多少东西":

  • Declarative Mode:声明式路由,<BrowserRouter> + <Routes>,只覆盖 URL 匹配组件、导航与激活态;
  • Data Mode:路由配置移出 React 渲染(createBrowserRouter),增加 loaderactionuseFetcher 等数据能力;
  • Framework Mode:在 Data Mode 之上包裹 Vite 插件,额外提供类型安全的 href、类型安全的 Route Module API、智能代码分割,以及 SPA / SSR / 静态预渲染策略。

官方模式文档给出了明确的选型建议:如果你"还没有形成偏好"、在对比 Next.js / Solid Start / SvelteKit 等方案、想从 Next.js 迁移,或"就是想在 React 上快速构建",Framework Mode 是推荐起点。完整对比见 模式选择文档Framework 入门索引

识别一个 Framework Mode 应用:文件与约定

Framework 应用有一套可识别的文件形状(shape)。默认 appDirectoryapp,但在假设具体路径之前,应先查看 react-router.config.ts 的配置。典型的 Framework 应用包含:

react-router.config.ts
app/root.tsx
app/routes.ts
app/routes/**/*.tsx
route modules importing from ./+types/...

仓库自带的 playground/framework 就是一个标准样本。它的 vite.config.ts 只做了两件事——启用 TS 路径解析并挂载官方插件:

import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";

export default defineConfig({
  resolve: { tsconfigPaths: true },
  plugins: [reactRouter()],
});

react-router.config.ts 展示了配置入口的形态(含子资源完整性校验与 future 标志):

import type { Config } from "@react-router/dev/config";

export default {
  subResourceIntegrity: true,
  future: {
    unstable_optimizeDeps: true,
  },
} satisfies Config;

routes.ts 使用了最简手动配置:

import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
  index("routes/_index.tsx"),
  route("products/:id", "routes/product.tsx"),
] satisfies RouteConfig;

判断一个项目是否为 Framework Mode 应用,可对照这组信号:根目录有 react-router.config.tsvite.config.ts 引入了 @react-router/dev/vitereactRouter() 插件、app/ 目录下存在 routes.tsroot.tsx、路由模块内部从 ./+types/... 导入类型。

路由配置:routes.ts 手动配置与 flatRoutes() 文件系统路由

Framework 应用的路由统一配置在 app/routes.ts。每条路由由两部分组成:URL 模式路由模块文件路径。配置函数由 @react-router/dev/routes 导出,从 routes.ts 源码 可以看到其完整导出为 routeindexlayoutprefixrelativegetAppDirectory,类型包括 RouteConfigRouteConfigEntry

基础写法:route 与 index

最小可用配置(即 playground/framework 的写法):

import { type RouteConfig, route, index } from "@react-router/dev/routes";

export default [
  index("./home.tsx"),                 // 渲染到根路由 Outlet 的 /
  route("products/:pid", "./product.tsx"),
] satisfies RouteConfig;

一个更大的示例(来自 routing 文档),展示 layoutprefix 的组合:

import {
  type RouteConfig,
  route,
  index,
  layout,
  prefix,
} from "@react-router/dev/routes";

export default [
  index("./home.tsx"),
  route("about", "./about.tsx"),

  layout("./auth/layout.tsx", [
    route("login", "./auth/login.tsx"),
    route("register", "./auth/register.tsx"),
  ]),

  ...prefix("concerts", [
    index("./concerts/home.tsx"),
    route(":city", "./concerts/city.tsx"),
    route("trending", "./concerts/trending.tsx"),
  ]),
] satisfies RouteConfig;

各辅助函数语义:

  • route(path, file, children?):常规路由,路径段自动拼到父级之下;
  • index(file):索引路由,渲染到父路由的 <Outlet> 且位于父路由 URL,不能再有子路由;
  • layout(file, children):布局路由,为子路由建立新的嵌套层级,但不向 URL 追加任何路径段——类似可以放在任意层级的"根路由";
  • prefix("xxx", routes):给一组路由加路径前缀,本身不引入新路由节点,prefix("parent", [route("child1", ...)]) 等价于 route("parent/child1", ...)

嵌套路由

export default [
  // parent route
  route("dashboard", "./dashboard.tsx", [
    // child routes
    index("./home.tsx"),
    route("settings", "./settings.tsx"),
  ]),
] satisfies RouteConfig;

父级路径会自动包含在子路由中,上述配置同时生成 /dashboard/dashboard/settings;子路由通过父组件中的 <Outlet/> 渲染。

路径段:动态、可选与 Splat

  • 动态段:以 : 开头(如 route("teams/:teamId", "./team.tsx")),匹配后解析为 params.teamIdloader/action/组件 props 使用,可一条路由含多个动态段;
  • 可选段:段尾加 ?(如 route(":lang?/categories", ...)route("users/:userId/edit?", ...));
  • Splat:以 /* 结尾的路径匹配其后任意字符(含 /),剩余部分位于 params["*"],常重命名为 splatroute("*", "./catchall.tsx") 可作为全局 404 兜底,在 loader 中 throw new Response("Page not found", { status: 404 })

文件系统路由:flatRoutes()

如果偏好"文件命名即路由",可用 @react-router/fs-routes 包的 flatRoutes()。其实现位于 flatRoutes.ts,从源码可以看到它支持的约定:路由模块扩展名为 .js/.jsx/.ts/.tsx/.md/.mdx,参数前缀字符 $、转义字符 []、可选段 (),并内部使用 PrefixLookupTrie(前缀查找字典树)高效匹配路由前缀。两种风格可混用:

import { type RouteConfig, route } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";

export default [
  route("/", "./home.tsx"),
  ...(await flatRoutes()),
] satisfies RouteConfig;

文件系统路由的完整命名约定见 file-route-conventions

组件路由(补充说明)

也可以在任何组件树中直接使用 <Routes>/<Route> 做元素级路由,但这类路由不参与数据加载、action、代码分割等路由模块能力,使用场景比路由模块更受限。

Route Modules:Framework Mode 的核心单元

routes.ts 中引用的每个文件就是一个路由模块。它是自动代码分割、数据加载、actions、revalidation、错误边界等框架特性的载体。仓库示例 product.tsx 展示了标准形态:

import type { Route } from "./+types/product";

export function loader({ params }: Route.LoaderArgs) {
  return { name: `Super cool product #${params.id}` };
}

export default function Component({ loaderData }: Route.ComponentProps) {
  return <h1>{loaderData.name}</h1>;
}

一个典型的、更完整的模块示例(参考文档给出):

import type { Route } from "./+types/product";

export async function loader({ params }: Route.LoaderArgs) {
  return { product: await getProduct(params.productId) };
}

export default function Product({ loaderData }: Route.ComponentProps) {
  return <h1>{loaderData.product.name}</h1>;
}

全部导出项速查表

导出 用途
default 路由匹配时渲染的路由组件
loader SSR / 预渲染 / 服务端数据请求时的服务端数据加载
clientLoader 仅浏览器端的数据加载,或为服务端 loader 数据做补充
action 服务端变更,由 <Form>useSubmit 或 fetchers 触发
clientAction 仅浏览器端的变更,或服务端 action 的客户端包装
ErrorBoundary 本路由 loader/action/组件抛错时的 UI
HydrateFallback 客户端 loader 水合运行期间的初始回退 UI
links / meta 路由级文档 <link> 与元数据
handle 任意路由元数据,通过 useMatches 消费
shouldRevalidate 覆盖默认的 loader 重验证行为
middleware / clientMiddleware 启用时的服务端/客户端请求管道钩子

各项实现细节可对照 Route Module 官方文档,其中有 middleware/clientMiddlewareheadershandlelinksmetaHydrateFallback 的完整代码示例。

ComponentProps:props 替代 hooks

default 组件渲染时自动接收 Route.ComponentProps 中定义的四组 props,可替代 useLoaderData/useParams 等 hooks,且类型自动正确:

import type { Route } from "./+types/route-name";

export default function MyRouteComponent({
  loaderData,
  actionData,
  params,
  matches,
}: Route.ComponentProps) {
  // loaderData: 本模块 loader 的返回值
  // actionData: 本模块 action 的返回值
  // params:     路由参数
  // matches:    当前路由树的完整匹配数组
  return <div>...</div>;
}

服务端数据的调用位置约束

SSR / 服务端数据路由中,Node-only 或数据库代码必须放在 server-only 模块中,并从 loader/action 调用,绝不能从浏览器渲染的组件代码中调用。客户端数据加载的完整规则(含 clientLoader.hydrate = true as const 参与首屏水合的写法)见 data-loading 文档

根路由与布局规则

app/root.tsx 是根路由,所有 routes.ts 中的路由都嵌套在它内部。全局文档/应用壳层面的关注点应放在这里:全局 Provider、应用级导航、页脚、scripts/meta/links、文档结构。分区级布局应使用嵌套/布局路由实现,不要把本应共享 UI 或数据边界的路由拍平。

playground/frameworkroot.tsx 是很好的模板参考——Layout 组件提供 <html>/<head>/<body> 结构,头部挂载 <Meta /><Links />(聚合所有路由的 meta 与 links 导出),导航链接使用 prefetch="intent",底部挂载 <ScrollRestoration /><Scripts />

import {
  Link, Links, Meta, Outlet, Scripts, ScrollRestoration,
} from "react-router";

export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <ul>
          <li><Link prefetch="intent" to="/">Home</Link></li>
          <li><Link prefetch="intent" to="/products/abc">Product</Link></li>
        </ul>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

export default function App() {
  return <Outlet />;
}

特殊文件的职责说明见 special-files 文档

数据加载与变更(Mutations)框架规则

修改路由数据前先读 data-loadingactions。Framework 的核心规则:

  1. loaderclientLoader 加载路由数据;
  2. actionclientAction 变更路由数据;
  3. 路由数据优先用 loader/action,而不是临时的 useEffect 抓取——这才能纳入导航生命周期、pending 状态与自动重验证;
  4. 按文档规范使用 data() / Responses 与 redirect;
  5. 除非文档指向 shouldRevalidate,否则让 React Router 在 action 之后自动重验证。注意:Framework + SSR 下,loader 在每次导航与表单提交后默认自动重验证,这与 Data Mode 的行为不同(SPA Mode 下 shouldRevalidate 行为则与 Data Mode 一致);
  6. 服务端路由保持 server-only 代码在独立模块中,仅从 loader/action 调用。

三个常见模式

  • action 校验失败return data({ errors, values }, { status: 400 }),然后从 Route.ComponentProps["actionData"]fetcher.data 中读取并渲染错误;
  • loader 中记录不存在throw data("Not Found", { status: 404 }),由该路由的 ErrorBoundary 渲染;
  • 搜索/筛选数据:在 loader 中解析请求 URL 的 search params,让 URL 可分享、可收藏。

表单、Fetchers 与 Pending UI

涉及表单与 pending UI 时先读 actionspending-uifetchersform-vs-fetcher 解析。决策口诀:

场景 选择
更新 URL 的搜索/筛选表单 <Form method="get">
需要改变 URL/历史或完成后重定向的变更 <Form method="post">
用户应停留在同一页的变更 useFetcher / <fetcher.Form>
乐观 UI fetcher.formDatanavigation.formData 派生

<Form>useSubmit、fetcher 三者最终都会触发对应路由的 action 并在完成后自动重验证页面 loader 数据,差异主要在导航语义(是否推入历史、是否重定向、数据归属组件)。

类型安全:./+types 与生成类型规则

修改生成路由类型或 typed URL 行为前,先读 route-module-type-safetytype-safety 解析。硬性规则:

  1. 类型一律从 ./+types/<route> 导入(如 import type { Route } from "./+types/product");
  2. 使用 Route.LoaderArgsRoute.ActionArgsRoute.ComponentProps 等生成类型;
  3. 在合适处使用 type-only import;
  4. 不要手工编辑生成的 .react-router/types 文件——它们由 dev 工具链再生成。

playground/framework/app/routes/product.tsx 中对 params.id 的类型化访问(params: { id: string })正是生成类型带来的直接收益:动态段名称会精确推导进 loader 参数与组件 props 类型。

元数据(meta)注意事项

修改 meta 前读 meta how-to。关键点:

  • meta 接收 loaderData不要使用已废弃的 data 参数;
  • 最后一条匹配路由的 meta 生效,且整个描述符数组是替换而非合并,可自行构建跨路由层级的 meta 组合逻辑;
  • 自 React 19 起,官方文档同时推荐在路由组件中直接使用内置 <meta>/<title> 元素。

渲染策略:SSR、SPA 与预渲染可混合

Framework Mode 的渲染形态可以是 SSR、SPA、预渲染,或依配置与路由行为而混合。修改渲染行为前必读:

渲染策略决定了 loader 何时在服务端执行、clientLoader 是否参与首屏水合、以及部署产物形态,是 Framework 应用中改动前必须确认的配置层面问题。

中间件、Session 与认证

实现 middleware 或 auth/session 流之前,先读 middlewaresessions-and-cookies。特别注意:middleware 与 context API 对版本和配置敏感——实现前先确认当前安装的 React Router 版本与应用 react-router.config.ts 中的配置。服务端 middleware 在文档与数据请求前后顺序执行,next() 继续传递调用链,到达叶子路由时执行该导航的 loader/action;客户端 clientMiddleware 是其浏览器端对应物,但不返回 Response(它不包装 HTTP 请求)。

扩展:RSC Framework

如果该 Framework 应用使用了 unstable_reactRouterRSC@vitejs/plugin-rsc,则属于 RSC 变体,需额外阅读 React Server Components 指南。仓库中 playground/rsc-vite-framework 提供了完整的 RSC Framework 示例应用(含 react-router.config.tsstart.js 与 Vite 中间件启动脚本)可供参考。

上手路径:从模板到本地运行

Framework 项目最典型的起点是官方脚手架:

npx create-react-router@latest my-react-router-app
cd my-react-router-app
npm i
npm run dev

随后浏览器打开 http://localhost:5173 即可。脚手架包的实现(含交互式 prompts 与模板复制逻辑)位于 create-react-router 包,本仓库 playground/framework 则展示了模板生成的项目结构与依赖。修改任何 Framework 应用代码前,建议按"先读 start/framework 系列文档 → 确认 react-router.config.ts → 再动 routes.ts/路由模块"的顺序推进。

小结

Framework Mode 的整套工程契约可以浓缩为:vite.config.tsreactRouter() 插件 → react-router.config.ts 定义构建行为 → app/routes.ts 声明路由 → 每个路由模块通过 ./+types 获得端到端类型 → root.tsx 承载文档壳 → 渲染策略按配置混合 SSR/SPA/预渲染。掌握这套形状后,配合 how-toexplanation 文档(注意核对文档头部的 [MODES: framework, ...] 标记)中的具体条目,即可安全地对任何 Framework Mode 应用做数据、表单、类型与渲染层面的修改。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384