一文搞懂 React Router v7 升级:v6 和 Remix 项目迁移实战教程

267 阅读30分钟

说明:本文聚焦于升级迁移实战,关于 v7 的深度全栈特性(SSR 原理、loader/action 高级用法等)请参考《React Router v7 全栈开发指南》

一、React Router v7 简介

在 React 生态系统的发展历程中,2024 年 11 月 22 日注定是一个值得被铭记的日子——React Router v7 在这一天正式发布。这不仅仅是一次简单的版本迭代,更是一个具有里程碑意义的重大变革。它既是 v6 的自然延续和升级,更是 Remix 框架的"回归"之旅。在这个版本中,原本独立发展的 Remix v3 被完整地整合进了 React Router v7,最终实现了路由库与全栈框架的完美统一。

核心定位

如果要用一个简洁的公式来描述 React Router v7 的本质,那就是:

React Router v7 = React Router v6 + Remix 全栈能力

  • 继续作为库使用:就像使用 v6 那样进行纯客户端路由管理,享受完全的向下兼容
  • 启用框架模式:拥抱服务端渲染(SSR)、文件路由、类型安全等现代化的企业级特性

这种设计充分体现了"渐进式增强,而非破坏性升级"的核心理念,让开发者能够根据项目需求自由选择最合适的使用方式。

三种使用模式

v7 为开发者精心准备了三种灵活的模式选择:

1. 声明模式(传统路由)

这是我们最熟悉的传统方式,通过 <BrowserRouter><Routes> 组件来声明式地定义路由。

  • 完全兼容 v6 及之前版本的代码
  • 让老项目的升级几乎零成本
  • 适合纯客户端应用

2. 数据模式(配置式路由)

使用 createBrowserRouter 创建配置式路由,并结合强大的 loader 和 action 函数实现数据预取和变更。

  • 从 v6.4+ 版本开始引入的现代化路由模式
  • 支持数据预加载和表单提交处理
  • 提供更好的错误处理和加载状态管理

3. 框架模式(完整全栈)

这是 v7 带来的全新能力,也是最激动人心的特性。

  • 通过 react-router.config.ts 配置文件定义项目设置
  • 使用约定式的 app/routes/ 目录结构实现文件路由
  • 获得 SSR、SSG(静态站点生成)等全栈特性的支持
  • 本质上完整继承了 Remix 框架的所有优秀基因

阅读建议

根据你的使用场景选择阅读重点:

  • React Router v6 用户:重点阅读第二章(对比分析)和第四章(升级步骤)
  • Remix 用户:重点阅读第三章(功能映射)和第五章(迁移指南)
  • 新项目选型:建议完整阅读,全面了解 v7 的能力和适用场景

二、React Router v7 vs v6 对比

在深入升级细节之前,我们有必要先全面了解 React Router v7 相比 v6 带来了哪些实质性的变化。这不仅能帮助我们更好地理解升级的必要性,也能让我们在升级过程中有的放矢,做出更加明智的技术决策。

2.1 架构差异

从架构层面来看,v7 和 v6 之间存在着一些根本性的定位差异。React Router v6 本质上是一个纯粹的客户端路由库,它专注于在浏览器端管理路由状态和页面导航,至于构建工具的选择、服务端渲染的实现等问题,都需要开发者自行配置和处理。而 React Router v7 则将自己定位为一个"可选的全栈框架",这意味着它既可以像 v6 那样作为轻量级的路由库使用,也可以在开启框架模式后,提供完整的全栈开发支持。

在构建工具方面,v6 时代开发者需要自行选择和配置 Webpack、Rollup 或其他构建工具,而 v7 在框架模式下内置了对 Vite 的深度集成,为开发者提供了开箱即用的现代化构建体验。对于服务端渲染(SSR)支持,v6 需要开发者手动实现相当复杂的配置,而 v7 在框架模式下原生支持 SSR,大大降低了使用门槛。此外,v7 还引入了文件路由系统的支持,这在 v6 中是完全不存在的特性。在数据加载方面,虽然 v6.4+ 版本已经引入了 loader 和 action 的概念,但 v7 对这些功能进行了进一步的增强,特别是在类型安全方面有了质的飞跃。

下面是一个直观的对比表格:

维度React Router v6React Router v7
定位纯客户端路由库可选全栈框架
构建工具自行配置(Webpack 等)内置 Vite(框架模式)
SSR 支持需手动实现原生支持(框架模式)
文件路由不支持支持(框架模式)
数据加载loader/action(v6.4+)增强的 loader/action + 类型安全

2.2 路由定义方式对比

路由定义方式的演进是 React Router 发展历程中最直观的变化之一。让我们通过具体的代码示例来理解这些变化。

React Router v6 中,如果你使用的是 v6.4 及以上版本,官方推荐的做法是采用配置式路由。这种方式通过 createBrowserRouter 函数创建一个路由配置对象,然后使用 RouterProvider 组件来渲染整个应用。这种模式的优势在于可以配置 loader 函数来实现数据预加载,让数据获取和路由导航解耦:

// v6.4+ 推荐方式
import { createBrowserRouter, RouterProvider } from "react-router-dom";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    loader: rootLoader,
    children: [{ path: "users/:id", element: <User />, loader: userLoader }],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

而到了 React Router v7,路由定义方式变得更加灵活多样,开发者可以根据项目的实际需求和迁移成本来选择最合适的方式。v7 提供了三种不同的路由定义模式,每一种都有其特定的适用场景:

// 方式 1:声明式路由(完美兼容 v6 及更早版本的代码)
// 如果你的项目使用的是传统的声明式路由,v7 完全支持,无需任何修改
import { BrowserRouter, Routes, Route } from "react-router";
<BrowserRouter>
  <Routes>
    <Route path="/" element={<Home />} />
  </Routes>
</BrowserRouter>;

// 方式 2:数据模式(与 v6.4 保持一致,但包名得到了简化)
// 这种方式适合已经在使用 loader/action 的 v6.4+ 项目
import { createBrowserRouter, RouterProvider } from "react-router";
const router = createBrowserRouter([
  /* 路由配置... */
]);

// 方式 3:框架模式(这是 v7 的全新能力,通过文件系统定义路由)
// 在 react-router.config.ts 配置文件中定义路由映射
import { index, route } from "@react-router/dev/routes";
export default [index("./home.tsx"), route("users/:id", "./user.tsx")];

可以看到,第一种声明式方式确保了老项目的平滑升级,第二种数据模式让已经采用现代化路由模式的项目能够轻松迁移,而第三种框架模式则为那些希望拥抱全栈能力的项目提供了最强大的支持。

2.3 数据获取对比

数据获取方式的变化体现了 React Router 团队对开发体验的持续优化。让我们通过对比来理解这些改进。

React Router v6.4+ 中,当我们需要在路由加载时预先获取数据,需要使用框架提供的 json() 辅助函数来包装返回的数据对象。这个函数会帮我们创建一个带有正确 Content-Type 响应头的 Response 对象:

// v6.4+ 的典型写法
import { json } from "react-router-dom";

export async function loader({ params }) {
  const user = await fetchUser(params.id);
  return json({ user }); // 必须使用 json() 函数包装数据
}

而在 React Router v7 中,团队对这个 API 进行了大胆的简化。开发者现在可以直接返回 JavaScript 对象,框架会自动处理 JSON 序列化和响应头设置。这个看似微小的改变,实际上大大提升了代码的简洁性和可读性。更重要的是,在框架模式下,v7 能够自动进行类型推断,让 TypeScript 用户获得完整的类型安全保护:

// v7 的现代化写法 - 告别 json() 包装函数
export async function loader({ params }) {
  const user = await fetchUser(params.id);
  return { user }; // 直接返回对象,简洁明了
}

// 在框架模式下,TypeScript 用户可以享受完整的类型安全
export default function User() {
  const { user } = useLoaderData<typeof loader>(); // 自动推断出 user 的类型
  return <div>{user.name}</div>;
}

这种改进不仅让代码更加清爽,也减少了开发者需要记忆的 API 数量,让数据加载的逻辑回归本质——获取数据,返回数据,就这么简单。

2.4 API 变更清单

为了让升级过程更加顺利,我们需要明确了解哪些 API 发生了变化。这些变化主要分为三类:被废弃的 API、新增的 API,以及改名或移动的 API。

废弃的 API

随着框架的演进,一些曾经必需的辅助函数现在已经不再需要。React Router v7 废弃了 json()defer() 这两个在 v6 中常用的辅助函数。之所以能够废弃它们,是因为框架本身变得更加智能,能够自动处理这些场景:

APIv6 用法v7 替代方案
json()return json({ data })return { data }
defer()return defer({ promise })return { promise }(直接返回)

新增的 API

与此同时,v7 也引入了一些激动人心的新特性,为开发者提供了更强大的能力。这些新增的 API 主要围绕框架模式展开,让 React Router 能够胜任更复杂的全栈开发场景:

  • clientLoader:这是一个专门用于客户端数据加载的新函数,允许你在客户端导航时执行特定的数据获取逻辑,与服务端的 loader 形成互补
  • @react-router/dev:全新的开发工具包,包含了框架模式所需的 CLI 命令和 Vite 插件,是启用框架模式的核心依赖
  • @react-router/node:专为 Node.js 环境设计的适配器,处理服务端渲染相关的逻辑
  • View Transitions 支持:通过 <Link viewTransition> 属性,你可以轻松实现页面切换时的流畅过渡动画,提升用户体验

改名/移动的 API

在包结构优化的过程中,一些 API 的命名和归属也发生了调整。最显著的变化是包名的简化——原本需要从 react-router-dom 导入的 API,现在推荐直接从 react-router 导入。虽然 react-router-dom 在 v7 中依然可用,但使用统一的 react-router 包名能够让代码更加简洁一致。对于服务端渲染相关的功能,v7 提供了专门的 @react-router/node 包来处理,这比 v6 时代需要手动实现要方便得多:

变化类型v6v7
包名简化react-router-domreact-router(推荐)
服务端渲染包手动实现@react-router/node

2.5 包结构变化

包结构的调整是 v7 升级过程中需要特别关注的一个方面。理解这些变化能够帮助我们更好地规划升级策略。

react-router-dom 的变化

在 React Router v6 时代,react-router-dom 是我们最常安装的包,它包含了所有在浏览器环境中进行路由管理所需的功能。到了 v7,官方对包结构进行了重新设计和优化。现在,你有两种选择:可以安装新的 react-router 包(这是官方推荐的做法,因为它包含了所有功能且命名更加简洁),也可以继续使用 react-router-dom@7(为了保持向下兼容,这个包仍然可用)。两种方式在功能上完全等价,但从长远来看,统一使用 react-router 会让代码库更加一致:

# v6 的安装方式
npm install react-router-dom

# v7 提供了两种安装方式,功能完全相同
npm install react-router         # 官方推荐,命名更简洁
npm install react-router-dom@7   # 仍然可用,保持兼容性

@react-router/* 系列新包介绍

除了核心路由包的变化,v7 还引入了一系列以 @react-router/ 为命名空间的新包。这些包各司其职,为不同的使用场景和部署环境提供专门的支持。了解这些包的用途,能够帮助我们在实际项目中做出正确的技术选型:

@react-router/dev          # 框架模式的开发工具包,包含 CLI 命令和 Vite 插件
@react-router/node         # Node.js 环境适配器,用于处理服务端渲染
@react-router/cloudflare   # Cloudflare Workers 适配器,支持边缘计算部署
@react-router/express      # Express.js 集成包,方便与现有 Express 应用整合

这些新包的引入,让 React Router v7 能够更好地适应现代 Web 应用多样化的部署需求,无论是传统的 Node.js 服务器,还是新兴的边缘计算平台,都能找到对应的官方支持方案。

三、React Router v7 vs Remix 对比

对于 Remix 用户来说,理解 React Router v7 与 Remix 之间的关系至关重要。这不是简单的版本升级,而是两个优秀框架的融合与统一。

3.1 Remix 的"落幕"与融入

为什么 Remix 要融入 React Router?

这个决定的背后有着深思熟虑的战略考量。Remix 团队在长期的实践中发现了几个关键问题:首先,许多开发者在选择技术栈时,面对"React Router 还是 Remix"这个问题时常常感到困惑——它们看起来解决的是类似的问题,但定位和能力又有所不同,这种选择上的焦虑影响了开发者的决策效率。其次,Remix 在 loader/action、服务端渲染等方面积累的优秀实践和核心价值,不应该仅仅局限于 Remix 用户,而应该惠及更广泛的 React Router 用户群体。最后,维护两个相似但独立的品牌会导致生态系统的割裂,统一品牌可以集中资源,为社区提供更好的支持。

融合之路的时间线

这次融合并非突然发生,而是经过了精心的规划和逐步的演进:

  • Remix v2 阶段:引入了 Vite 作为构建工具,这是为最终融合所做的重要技术准备,让 Remix 的构建体系与 React Router 更加接近
  • React Router v6.4 阶段:引入了 loader 和 action 概念,这是 React Router 向 Remix 靠拢的关键一步,让纯路由库开始具备数据管理能力
  • React Router v7 = Remix v3:最终完成了完整的融合,Remix v3 的所有能力都被整合进了 React Router v7

3.2 功能映射对照表

对于熟悉 Remix 的开发者来说,最关心的问题莫过于:"我在 Remix 中使用的那些概念和 API,在 React Router v7 中对应的是什么?"下面这个对照表清晰地展示了两者之间的映射关系,你会发现绝大多数概念都是一一对应的,这也是为什么迁移成本如此之低的原因:

Remix 概念React Router v7 对应说明
app/routes/app/routes/文件路由目录
remix.config.jsreact-router.config.ts配置文件
@remix-run/reactreact-router客户端 API
@remix-run/node@react-router/node服务端适配器
@remix-run/dev@react-router/dev开发工具
remix devreact-router dev开发命令

3.3 主要差异点

项目结构差异

# Remix                      # React Router v7
remix.config.js     →        react-router.config.ts
app/                         app/
├── routes/                  ├── routes/
├── entry.client.tsx         ├── entry.client.tsx
├── entry.server.tsx         ├── entry.server.tsx
└── root.tsx                 └── root.tsx

配置文件差异

// remix.config.js
export default {
  serverModuleFormat: "esm",
  future: { v3_fetcherPersist: true },
};

// react-router.config.ts(几乎相同)
import type { Config } from "@react-router/dev/config";
export default {
  ssr: true,
  // Remix 的 future flags 在 v7 中已默认启用
} satisfies Config;

部署适配器变化

Remix 适配器React Router v7 对应
@remix-run/cloudflare@react-router/cloudflare
@remix-run/deno@react-router/deno
@remix-run/express@react-router/express
@vercel/remix@vercel/remix(暂时兼容)

3.4 Remix 用户需要注意的变化

虽然 React Router v7 与 Remix 在功能上高度一致,但在迁移过程中还是有一些关键的变化点需要特别注意。提前了解这些变化,可以让迁移过程更加顺利:

  1. 包名的全局替换:这是最直观也是工作量最大的一项变更。所有从 @remix-run/* 命名空间导入的内容,都需要替换为 @react-router/* 或者直接使用 react-router。好在这个替换工作是机械性的,可以通过批量查找替换来快速完成

  2. 配置文件的重命名:原本的 remix.config.js 需要重命名为 react-router.config.ts。注意文件扩展名也从 .js 变成了 .ts,体现了对 TypeScript 的更好支持

  3. 命令行工具的变化:开发时使用的命令从 remix dev 变成了 react-router dev,构建和启动命令也相应调整

  4. json() 函数的弃用:和 v6 升级到 v7 一样,loader 和 action 函数不再需要使用 json() 包装返回值,直接返回普通对象即可

  5. TypeScript 配置的调整:类型定义需要从 @remix-run/dev/config 改为使用 @react-router/dev/config

四、从 React Router v6 升级到 v7

了解了 v7 的新特性和变化之后,接下来我们进入实战环节——如何将现有的 v6 项目升级到 v7。这个过程可能比你想象的要简单得多,特别是如果你选择保持库模式的话。

4.1 升级前准备

在动手升级之前,做好充分的准备工作可以有效避免潜在的问题,让升级过程更加平稳。

1. 检查当前版本

首先,我们需要明确当前项目使用的 React Router 版本。在终端中运行以下命令:

npm list react-router-dom

这个命令会显示当前安装的 react-router-dom 版本号,帮助你了解升级的起点。

2. 确保已升级到 v6.4+

这是一个非常重要的前置条件。如果你的项目还在使用 v6.4 以下的版本,强烈建议先进行一次中间升级,升级到 v6.4 或更高版本。这是因为 v6.4 引入了 loader 和 action 等重要概念,这些概念在 v7 中得到了进一步的增强和简化。先升级到 v6.4+ 可以让最终升级到 v7 的过程更加顺利,也能让你的代码逐步适应新的数据管理模式。

3. 依赖兼容性检查

在升级之前,务必确认以下依赖版本满足要求:

  • React 版本:推荐使用 React 18 或更高版本,最低要求是 16.8(支持 Hooks 的版本)
  • Node.js 版本:如果要使用框架模式(SSR 等特性),需要 Node.js 18 或更高版本;如果只是库模式,要求相对宽松
  • TypeScript 版本:如果项目使用 TypeScript,建议升级到 5.1 或更高版本,以获得最佳的类型支持

4.2 升级路径选择

React Router v7 的一大优势在于它提供了灵活的升级路径,你可以根据项目的实际情况和需求选择最合适的方式。这里我们介绍两条主要的升级路径,每条路径都有其适用场景和代价。

路径 A:保持库模式(最小改动)

如果你的项目满足以下条件,这条路径将是最佳选择:

适合的场景:

  • 你的应用是纯粹的单页应用(SPA),所有渲染都在客户端完成
  • 项目暂时不需要 SEO 优化,或者已经通过其他方式解决了 SEO 问题
  • 团队希望以最小的成本完成升级,保持现有架构稳定运行

改动的范围:

选择这条路径的好处是改动范围非常有限,主要工作集中在更新包名和调整少量被废弃的 API 上。不需要重构项目结构,不需要学习新的概念,现有的代码逻辑几乎可以完全保留。这对于那些对稳定性要求较高、不急于采用新特性的项目来说,是一个理想的选择。

路径 B:迁移到框架模式(获得全栈能力)

如果你希望充分利用 v7 的新能力,这条路径值得考虑:

适合的场景:

  • 你的应用需要良好的 SEO 表现,比如内容类网站、电商平台等
  • 希望享受服务端渲染带来的首屏性能提升和更好的用户体验
  • 愿意拥抱文件路由、类型安全等现代化开发特性
  • 正在进行新项目开发,或者计划对现有项目进行较大规模的重构

改动的范围:

这条路径的改动范围相对较大,需要调整项目的目录结构,引入 Vite 作为构建工具,并且需要理解和适应框架模式的开发方式。虽然前期投入较多,但长期来看,你将获得更强大的功能支持和更好的开发体验。

4.3 库模式升级步骤(最小改动)

如果你选择了路径 A,恭喜你,接下来的升级过程将会非常顺利。我们将分步骤详细讲解每个环节。

4.3.1 更新依赖包

升级的第一步是更新 npm 包。这个过程很简单,但需要注意包名的变化:

# 第一步:移除旧版本的 react-router-dom
npm uninstall react-router-dom

# 第二步:安装 v7,这里推荐使用新的包名
npm install react-router@7

# 或者,如果你希望保持使用 react-router-dom 这个包名(完全兼容)
npm install react-router-dom@7

两种安装方式在功能上完全等价,选择哪一种主要看个人偏好。不过从长远来看,使用 react-router 这个更简洁的包名是官方推荐的做法。

4.3.2 修改导入路径

包更新完成后,接下来需要修改代码中的导入语句。如果你的项目文件较多,手动修改会比较繁琐,好在这个工作可以通过批量替换来完成:

// v6 的导入方式
import { Link, useNavigate, useLoaderData } from "react-router-dom";

// v7 推荐的导入方式(两种都可以,功能完全相同)
import { Link, useNavigate, useLoaderData } from "react-router";
// 或者保持使用 react-router-dom 也完全没问题
import { Link, useNavigate, useLoaderData } from "react-router-dom";

批量替换命令

为了提高效率,你可以使用命令行工具批量替换所有文件中的导入语句。下面提供了不同操作系统的命令:

# macOS/Linux 系统使用 sed 命令
find src -type f -name "*.tsx" -o -name "*.jsx" | xargs sed -i '' 's/from "react-router-dom"/from "react-router"/g'

# Windows 系统使用 PowerShell
Get-ChildItem -Path src -Recurse -Include *.tsx,*.jsx | ForEach-Object { (Get-Content $_.FullName) -replace 'from "react-router-dom"', 'from "react-router"' | Set-Content $_.FullName }

执行这些命令前,建议先提交一次代码,以便在出现问题时可以快速回滚。

4.3.3 处理废弃 API

v7 废弃了几个在 v6 中常用的辅助函数,主要是 json()defer()。虽然这些函数在 v7 中仍然可以工作(会有警告),但最好在升级时一并处理,以避免未来的兼容性问题。

1. 移除 json() 包装

这是最常见的一个改动。在 v6 中,loader 函数通常会用 json() 来包装返回的数据,现在可以直接返回普通对象了:

// v6 的写法
import { json } from "react-router-dom";
export async function loader() {
  return json({ data: "hello" });
}

// v7 的简化写法
export async function loader() {
  return { data: "hello" }; // 直接返回对象,简单明了
}

// 特殊情况:如果需要自定义响应头,可以使用原生的 Response.json()
export async function loader() {
  return Response.json(
    { data: "hello" },
    { headers: { "Cache-Control": "max-age=3600" } }
  );
}

大部分情况下,你只需要删除 json() 的导入和调用即可。只有在需要设置自定义响应头时,才需要使用原生的 Response.json()

2. 移除 defer() 包装

如果你的代码中使用了 defer() 来实现流式数据加载(streaming),处理方式与 json() 类似:

// v6 使用 defer() 实现流式加载
import { defer } from "react-router-dom";
export async function loader() {
  return defer({
    critical: await fetchCritical(), // 等待关键数据
    lazy: fetchLazy(), // 延迟加载的 Promise
  });
}

// v7 直接返回,框架自动处理
export async function loader() {
  return {
    critical: await fetchCritical(),
    lazy: fetchLazy(), // 直接返回 Promise,无需包装
  };
}

v7 的框架内部会自动识别返回对象中的 Promise,并正确处理流式加载的逻辑,所以我们不再需要 defer() 这个额外的包装函数。

4.3.4 测试与验证

完成上述修改后,不要急于发布到生产环境。充分的测试是确保升级成功的关键步骤:

npm run dev  # 启动开发服务器,观察是否有错误
npm run build # 执行生产构建,确保构建过程正常

验证检查清单

建议按照以下清单逐项检查,确保所有功能都正常工作:

  • 所有路由能够正常跳转,没有 404 错误
  • loader 和 action 函数正常工作,数据能够正确加载和提交
  • 嵌套路由的层级关系正确,子路由能够正确渲染
  • 错误边界能够正常捕获和显示错误信息
  • 如果使用 TypeScript,确保没有类型错误

只有当这些检查项全部通过,才说明升级真正完成。如果发现问题,可以根据错误信息逐个排查解决。

4.4 框架模式升级步骤

4.4.1 安装依赖与 CLI 工具

# 移除旧依赖
npm uninstall react-router-dom

# 安装框架模式依赖
npm install react-router@7
npm install -D @react-router/dev @react-router/node vite

4.4.2 添加配置文件

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

export default {
  ssr: true, // 启用 SSR
  async prerender() {
    return []; // SSG 预渲染路由(可选)
  },
} satisfies Config;

4.4.3 配置 Vite

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

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

4.4.4 调整目录结构

# 原结构(v6)
src/
├── App.tsx
├── routes/
│   ├── Home.tsx
│   └── About.tsx
└── main.tsx

# 新结构(v7 框架模式)
app/
├── routes/
│   ├── _index.tsx      # 首页
│   └── about.tsx       # /about
├── root.tsx            # 根布局
└── entry.client.tsx    # 客户端入口

4.4.5 创建入口文件

// app/root.tsx
import { Links, Meta, Outlet, Scripts } from "react-router";

export default function Root() {
  return (
    <html lang="zh">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  );
}
// app/entry.client.tsx
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <HydratedRouter />
    </StrictMode>
  );
});

4.4.6 更新 package.json 脚本

{
  "scripts": {
    "dev": "react-router dev",
    "build": "react-router build",
    "start": "react-router-serve ./build/server/index.js"
  }
}

4.5 代码迁移示例

路由定义迁移

// v6 - 配置式路由
const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <Home /> },
      { path: "about", element: <About /> },
    ],
  },
]);

// v7 框架模式 - 文件路由
// app/routes/_index.tsx
export default function Home() {
  return <h1>首页</h1>;
}

// app/routes/about.tsx
export default function About() {
  return <h1>关于</h1>;
}

Loader 迁移示例

// v6
import { json } from "react-router-dom";

export async function loader({ params }) {
  const user = await fetchUser(params.id);
  return json({ user });
}

// v7
export async function loader({ params }) {
  const user = await fetchUser(params.id);
  return { user }; // 直接返回
}

4.6 常见问题与解决方案

问题 1:TypeScript 类型错误

# 错误:找不到类型定义
# 解决:确保安装了类型定义包
npm install -D @types/react @types/react-dom

问题 2:Vite 配置冲突

如果已有 Vite 配置,需要整合:

// vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    reactRouter(), // 必须在 react 插件之前
    react(),
  ],
});

问题 3:构建错误排查

# 清除缓存
rm -rf node_modules .vite build
npm install

# 重新构建
npm run build

五、从 Remix 升级到 React Router v7

5.1 升级前准备

1. 确保 Remix 版本为 v2.x

npm list @remix-run/react

如果是 v1.x,请先升级到 v2.x。

2. 开启所有 future flags

// remix.config.js(v2)
export default {
  future: {
    v3_fetcherPersist: true,
    v3_relativeSplatPath: true,
    v3_throwAbortReason: true,
  },
};

3. 依赖审计

确保所有 @remix-run/* 包版本一致。

5.2 使用官方迁移工具

自动迁移(推荐)

npx @react-router/upgrade

工具会自动完成:

  • 更新 package.json 依赖
  • 重命名配置文件
  • 批量替换导入语句
  • 调整文件结构

5.3 手动迁移步骤

5.3.1 更新 package.json 依赖

{
  "dependencies": {
    // 移除 Remix 包
    // "@remix-run/react": "^2.0.0",
    // "@remix-run/node": "^2.0.0",

    // 添加 React Router v7
    "react-router": "^7.0.0"
  },
  "devDependencies": {
    // "@remix-run/dev": "^2.0.0",

    "@react-router/dev": "^7.0.0",
    "@react-router/node": "^7.0.0"
  }
}
npm install

5.3.2 重命名配置文件

mv remix.config.js react-router.config.ts
// react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
  ssr: true,
  // Remix v2 的配置大部分直接兼容
} satisfies Config;

5.3.3 更新导入语句

批量替换命令

# macOS/Linux
find app -type f \( -name "*.tsx" -o -name "*.ts" \) -exec sed -i '' \
  -e 's/@remix-run\/react/react-router/g' \
  -e 's/@remix-run\/node/react-router/g' \
  -e 's/@remix-run\/cloudflare/@react-router\/cloudflare/g' \
  {} +

# Windows (PowerShell)
Get-ChildItem -Path app -Recurse -Include *.tsx,*.ts | ForEach-Object {
  (Get-Content $_.FullName) `
    -replace '@remix-run/react', 'react-router' `
    -replace '@remix-run/node', 'react-router' |
  Set-Content $_.FullName
}

5.3.4 适配器迁移

# 移除 Remix 适配器
npm uninstall @remix-run/express

# 安装 React Router 适配器
npm install @react-router/express
// server.js
// Remix
// import { createRequestHandler } from "@remix-run/express";

// React Router v7
import { createRequestHandler } from "@react-router/express";

5.3.5 更新 package.json 脚本

{
  "scripts": {
    // "dev": "remix vite:dev",
    // "build": "remix vite:build",
    // "start": "remix-serve build/server/index.js",

    "dev": "react-router dev",
    "build": "react-router build",
    "start": "react-router-serve ./build/server/index.js"
  }
}

5.4 代码迁移示例

entry.client.tsx 迁移

// Remix
import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <RemixBrowser />
    </StrictMode>
  );
});

// React Router v7(几乎相同)
import { HydratedRouter } from "react-router/dom";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <HydratedRouter />
    </StrictMode>
  );
});

路由文件迁移

// Remix
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";

export async function loader({ params }: LoaderFunctionArgs) {
  const user = await fetchUser(params.id);
  return json({ user }); // Remix 使用 json()
}

export default function User() {
  const { user } = useLoaderData<typeof loader>();
  return <h1>{user.name}</h1>;
}

// React Router v7
import { type LoaderFunctionArgs } from "react-router";
import { useLoaderData } from "react-router";

export async function loader({ params }: LoaderFunctionArgs) {
  const user = await fetchUser(params.id);
  return { user }; // v7 直接返回对象
}

export default function User() {
  const { user } = useLoaderData<typeof loader>();
  return <h1>{user.name}</h1>; // 组件代码完全相同
}

5.5 常见问题与解决方案

问题 1:样式处理变化

Remix v2 和 React Router v7 的 CSS 处理方式相同,无需改动。

问题 2:Meta 函数变化

// Remix v2 和 React Router v7 完全相同
export function meta({ data }) {
  return [
    { title: data.title },
    { name: "description", content: data.description },
  ];
}

问题 3:环境变量处理

# .env 文件处理方式相同
VITE_API_URL=https://api.example.com

六、生产环境部署

完成升级后,你需要将应用部署到生产环境。本章介绍使用 EdgeOne Pages 进行部署的完整流程。EdgeOne Pages 是一个边缘计算平台,支持 React Router v7 的多种渲染模式。

6.1 平台特点

EdgeOne Pages 是腾讯云推出的边缘全栈应用平台,支持 SPA、SSR、SSG 等多种渲染模式。

主要特性

  • 边缘网络:基于 CDN 的全球节点分发
  • 自动化部署:集成 Git 仓库,代码提交自动构建
  • 无服务器架构:无需手动配置服务器和进程
  • 网络优化:针对中国大陆网络环境优化
  • 灵活配置:支持 SPA、SSR、SSG 多种渲染模式

6.2 基础配置

6.2.1 安装适配器

npm install @edgeone/react-router

6.2.2 配置 Vite

// vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import { edgeoneAdapter } from "@edgeone/react-router";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    reactRouter(),
    edgeoneAdapter(), // 添加 EdgeOne 适配器
  ],
});

6.2.3 部署方式

方式一:Git 仓库部署(推荐)

通过连接 Git 仓库实现自动化部署:

  1. 将代码推送到 GitHub/GitLab/Gitee
  2. 访问 EdgeOne Pages 控制台
  3. 点击"创建项目",选择"导入 Git 仓库"
  4. 授权并选择你的仓库
  5. 平台会自动检测 React Router v7 项目并配置构建命令
  6. 点击"部署",等待构建完成

后续每次 Git 推送都会自动触发重新部署。

方式二:CLI 部署

如果你更喜欢命令行操作,可以使用 EdgeOne CLI:

# 安装 CLI 工具
npm install -g @edgeone/cli

# 登录账号
edgeone login

# 部署项目
edgeone pages deploy

6.3 渲染模式配置

EdgeOne Pages 支持三种渲染模式,可以根据项目需求在 react-router.config.ts 中配置:

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

export default {
  // SSR 模式(默认,推荐)
  // 适用于需要 SEO 优化和快速首屏加载的应用
  ssr: true,

  // SSG 模式(静态站点生成)
  // 适用于内容相对固定的网站(如博客、文档站)
  // async prerender() {
  //   return ["/", "/about", "/contact"];
  // },

  // SPA 模式(纯客户端渲染)
  // 适用于后台管理系统等不需要 SEO 的应用
  // ssr: false,
} satisfies Config;

6.4 环境变量配置

如果你的应用需要环境变量(如 API 地址),可以在 EdgeOne Pages 控制台中配置:

  1. 进入项目设置页面
  2. 找到"环境变量"选项
  3. 添加变量,如 VITE_API_URL=https://api.example.com
  4. 重新部署项目使配置生效

注意:所有客户端可访问的环境变量必须以 VITE_ 为前缀。

6.5 域名配置

部署成功后,平台会提供一个默认域名(如 your-project.pages.edgeone.ai)。绑定自定义域名的步骤:

  1. 在项目设置中找到"自定义域名"
  2. 添加你的域名(如 www.example.com
  3. 按照提示配置 DNS 记录
  4. 等待 SSL 证书自动签发(通常几分钟内完成)

6.6 部署检查

部署完成后的验证清单:

  • 访问部署的 URL,确认页面正常显示
  • 检查所有路由是否可以正常访问
  • 如果是 SSR 模式,查看网页源代码确认 HTML 包含实际内容
  • 测试 loader 和 action 功能是否正常工作
  • 检查浏览器控制台,确保没有错误信息

6.7 参考文档

更多部署相关的文档:

七、踩坑记录与经验总结

7.1 升级过程常见坑点

  1. 忘记移除 json() 包装

    • 症状:代码仍能运行,但控制台警告 json() is deprecated
    • 解决:全局搜索 import { json } 并移除
  2. Vite 配置顺序错误

    • 症状:构建失败或 HMR 不生效
    • 解决:确保 reactRouter() 插件在其他插件之前
  3. TypeScript 类型不匹配

    • 症状:useLoaderData() 类型推断失败
    • 解决:使用 typeof loader 显式指定类型
  4. 嵌套路由 Outlet 遗漏

    • 症状:子路由不显示
    • 解决:确保父组件渲染 <Outlet />

7.2 生产环境注意事项

  1. 构建产物验证

    npm run build
    ls -lh build/  # 检查输出文件
    
  2. SSR 渲染测试

    npm run start
    curl http://localhost:3000 # 检查 HTML 是否包含内容
    
  3. 环境变量检查

    • 确保所有 VITE_* 前缀的环境变量已配置
  4. 适配器兼容性

    • 不同部署平台的适配器可能需要额外配置

7.3 回滚策略

如果升级后遇到严重问题:

# 1. 切换到升级前的分支
git checkout main

# 2. 或者回退提交
git revert HEAD

# 3. 重新安装依赖
npm install

建议:在独立分支进行升级,测试通过后再合并。

八、总结与展望

8.1 升级收益总结

收益维度v6 → v7Remix → v7
学习成本低(API 几乎兼容)极低(几乎零改动)
性能提升可选(启用框架模式后明显)持平
开发体验类型安全增强、Vite 集成统一生态
生态兼容性更好(React Router 生态)更好
未来保障持续更新、RSC 支持计划官方重点支持

8.2 React Router 未来路线图

根据官方透露:

  1. React Server Components(RSC)支持:预计 v7.x 后续版本
  2. 更好的 Streaming SSR:持续优化
  3. 边缘计算优化:Cloudflare/Deno Deploy 深度集成
  4. 开发工具增强:更强大的 DevTools

8.3 相关资源与参考链接

附录:快速升级检查清单

v6 → v7 升级检查清单

  • 更新依赖:npm install react-router@7
  • 替换导入:react-router-domreact-router
  • 移除 json() 包装
  • 移除 defer() 包装
  • 测试所有路由
  • 测试 loader/action
  • 生产构建测试

Remix → v7 升级检查清单

  • 运行自动迁移工具:npx @react-router/upgrade
  • 或手动替换:@remix-run/*react-router/@react-router/*
  • 重命名配置文件:remix.config.jsreact-router.config.ts
  • 更新 package.json 脚本
  • 移除 json() 包装
  • 测试适配器(Express/Cloudflare 等)
  • 验证环境变量
  • 生产部署测试

最后建议:React Router v7 的升级过程非常友好,特别是对于 Remix 用户几乎是零成本。对于 v6 用户,建议采用渐进式策略:先升级依赖,保持库模式运行;待稳定后,再逐步启用框架模式享受全栈能力。升级后你将获得更好的类型安全、更现代的开发体验,以及持续的官方支持!