很多 React 老项目里,路由代码还在从 react-router-dom 里导 BrowserRouter、Link、Routes、useNavigate。
React Router v8 发布后,这个习惯要改了:react-router-dom 被移除,React Router v6 和 Remix v2 也进入 EOL。这次不算大重构,但会把 Node、React、Vite 和 import 写法一起带出来检查。
这篇不做发布公告复读,直接按老项目迁移的思路走一遍。
React Router v8 到底变了什么
React Router v8 在 2026 年 6 月 17 日发布。对业务项目来说,重点不是多了几个新 API,而是旧包和旧运行环境开始被清出主线。
几个变化和日常项目最相关:
react-router-dom被移除;@remix-run/*包被移除;- 只支持 ESM;
- 需要 Node.js 22.12+;
- 需要 React 19.2+;
- Vite 项目需要 Vite 7+;
- React Router v6 和 Remix v2 进入 EOL,不再接收安全更新。
如果你的项目已经在 React Router v7 上,迁移通常不复杂。最常见的改动就是把从 react-router-dom 导入的内容迁到 react-router 或 react-router/dom。
但如果你的项目还停在 v6,甚至更早版本,就别想着一步到位只改一个版本号。先升到 v7,把 deprecated 用法清掉,再升 v8,问题会少很多。
升级前先看项目属于哪种模式
React Router 现在不只是一个路由库,它有几种使用方式。升级前先判断项目是哪一种,后面改动范围会更清楚。
第一种是最常见的 Declarative Mode,也就是传统 SPA 写法:
tsx
import { BrowserRouter, Route, Routes } from "react-router-dom";
export function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</BrowserRouter>
);
}
第二种是 Data Mode,用 createBrowserRouter、RouterProvider,路由里会有 loader、action:
tsx
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
loader: async () => {
return fetch("/api/user");
},
},
]);
export function App() {
return <RouterProvider router={router} />;
}
第三种是 Framework Mode,也就是以前很多 Remix 项目迁过来的形态,会用到 @react-router/dev、文件路由、服务端渲染等能力。
先查依赖:
bash
npm ls react-router react-router-dom @react-router/dev @remix-run/react
pnpm 项目可以这样看:
bash
pnpm list react-router react-router-dom @react-router/dev @remix-run/react
如果只看到 react-router-dom,通常就是普通 SPA。如果同时看到 @react-router/dev 或 @remix-run/*,就要按 Framework Mode 的升级路线处理。
先确认运行环境能不能撑住 v8
这一步别跳过。React Router v8 的坑,很多不是路由代码本身,而是运行环境不达标。
先看 Node 版本:
bash
node -v
如果低于 Node.js 22.12,先别急着升级 React Router。你要同步检查本地开发机、CI、Dockerfile、部署平台的 Node 版本。否则本地跑通了,CI 一构建还是挂。
再看 React 版本:
bash
npm ls react react-dom
React Router v8 要求 React 19.2+。如果你的项目还在 React 18,升级就不只是路由层面的事了。这个时候我会单独开一条分支,先评估 React 19 的兼容性,再处理 React Router。
然后看 Vite 版本:
bash
npm ls vite
Vite 项目至少需要 Vite 7。现在很多项目已经在 Vite 6 或更低版本上跑了很久,package.json 里一堆插件也绑着老版本。不要只改 react-router,构建链路也要一起过一遍。
我一般会先开一个迁移分支:
bash
git checkout -b chore/upgrade-react-router-v8
升级类改动不要直接怼到业务分支里。路由是全站入口,一旦出问题,影响面会很大。
最常见的改动:删掉 react-router-dom
v8 里,react-router-dom 没了。
以前你可能这样写:
tsx
import {
BrowserRouter,
Link,
NavLink,
Route,
Routes,
useNavigate,
} from "react-router-dom";
迁移后要拆开:
tsx
import {
Link,
NavLink,
Route,
Routes,
useNavigate,
} from "react-router";
import { BrowserRouter } from "react-router/dom";
我会这样记:
- 通用路由 API,从
react-router导; - DOM 相关的 Router,比如
BrowserRouter、HashRouter,从react-router/dom导。
如果项目用的是 createBrowserRouter 和 RouterProvider,也类似:
tsx
import { createBrowserRouter, RouterProvider } from "react-router";
如果你想快速找出项目里还有多少旧导入,直接搜:
bash
rg "react-router-dom" src
Windows PowerShell 也一样能跑,只要装了 ripgrep:
powershell
rg "react-router-dom" src
没有 rg 的话,用编辑器全局搜索也行。别漏掉测试文件、storybook 文件、工具组件里的导入。
依赖也要删掉旧包:
bash
npm remove react-router-dom
npm i react-router@latest
pnpm 写法:
bash
pnpm remove react-router-dom
pnpm add react-router@latest
如果是 Framework Mode 项目,还要同步升级相关包:
bash
npm i react-router@latest @react-router/dev@latest
不要让 react-router 和 @react-router/dev 分别停在不同大版本。路由运行时和开发工具链错位,排错会很难受。
一个普通 SPA 迁移示例
假设原来入口是这样:
tsx
// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { App } from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
);
路由组件是这样:
tsx
// src/App.tsx
import { Link, Route, Routes } from "react-router-dom";
function Home() {
return (
<main>
<h1>首页</h1>
<Link to="/settings">去设置页</Link>
</main>
);
}
function Settings() {
return <h1>设置页</h1>;
}
export function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/settings" element={<Settings />} />
</Routes>
);
}
升级后入口改成:
tsx
// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router/dom";
import { App } from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
);
路由组件改成:
tsx
// src/App.tsx
import { Link, Route, Routes } from "react-router";
function Home() {
return (
<main>
<h1>首页</h1>
<Link to="/settings">去设置页</Link>
</main>
);
}
function Settings() {
return <h1>设置页</h1>;
}
export function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/settings" element={<Settings />} />
</Routes>
);
}
这个例子看起来改动很小,但真实项目里经常散落着几十个 Link、NavLink、useParams、useLocation、Navigate。所以迁移时不要凭感觉改,先全局搜,再逐个替换。
Data Router 项目怎么改
Data Router 项目常见入口是这样:
tsx
import {
createBrowserRouter,
Link,
RouterProvider,
} from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
},
{
path: "/profile/:id",
element: <Profile />,
loader: async ({ params }) => {
const response = await fetch(`/api/users/${params.id}`);
return response.json();
},
},
]);
export function App() {
return <RouterProvider router={router} />;
}
v8 里改成:
tsx
import {
createBrowserRouter,
Link,
RouterProvider,
} from "react-router";
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
},
{
path: "/profile/:id",
element: <Profile />,
loader: async ({ params }) => {
const response = await fetch(`/api/users/${params.id}`);
return response.json();
},
},
]);
export function App() {
return <RouterProvider router={router} />;
}
这里不是 API 变复杂了,而是要把导入来源统一掉。
如果你项目里有自定义封装,比如:
tsx
// src/router/index.ts
export {
Link,
NavLink,
useLocation,
useNavigate,
useParams,
} from "react-router-dom";
那反而好处理,改一处就能覆盖大部分业务代码:
tsx
// src/router/index.ts
export {
Link,
NavLink,
useLocation,
useNavigate,
useParams,
} from "react-router";
如果项目没有这种统一出口,这次迁移也可以顺手补一个。但别为了"优雅"大范围重构,先完成最小可验证迁移,后面再整理。
Framework Mode 项目要更谨慎
如果你的项目是从 Remix 迁过来的,或者已经用了 React Router 的 Framework Mode,迁移不只是改 import。
你可能会看到这些依赖:
json
{
"dependencies": {
"@react-router/node": "...",
"@react-router/serve": "...",
"react-router": "..."
},
"devDependencies": {
"@react-router/dev": "..."
}
}
这种项目升级前至少要做三件事:
bash
npm ls react-router @react-router/dev @react-router/node @react-router/serve
确认所有 @react-router/* 包在同一个大版本。
bash
rg "@remix-run/" .
确认项目里没有残留的 Remix 包导入。
bash
npm run build
确认 SSR、路由模块、构建产物没有因为 ESM-only 或环境版本变化挂掉。
如果项目还在 Remix v2,我不建议直接把版本号改到 v8。先走官方从 Remix 到 React Router 的迁移路线,再处理 v8。一步跨太大,遇到问题时很难判断是框架迁移、运行时版本,还是业务代码本身导致的。
我会重点检查这 5 个坑
1. CI 里的 Node 版本
本地 Node 版本经常比 CI 新。你本地 node -v 是 22,GitHub Actions、GitLab CI、Docker 镜像里可能还是 20。
看到这种配置就要改:
yaml
- uses: actions/setup-node@v4
with:
node-version: 20
至少要升到:
yaml
- uses: actions/setup-node@v4
with:
node-version: 22.12
团队项目里还要同步 .nvmrc、.node-version、Dockerfile 和部署平台配置。
2. React 版本没对齐
React Router v8 要求 React 19.2+。如果你只是升级了 react-router,但 react、react-dom 还停在 18,很容易出现依赖解析或运行时报错。
检查命令:
bash
npm ls react react-dom react-router
期望是同一棵依赖树里没有奇怪的重复版本。
3. 还有旧包导入
升级后跑一遍:
bash
rg "react-router-dom|@remix-run/" src
如果还有结果,就继续改。这里别只搜 src,有些项目的路由测试、mock、storybook、脚本文件也会引用旧包。
更完整一点可以搜全仓库:
bash
rg "react-router-dom|@remix-run/" .
4. TypeScript 报错被跳过
有些项目的构建命令只跑 Vite,不跑类型检查。迁移路由时,类型错误很容易藏在没被构建到的页面里。
我会单独跑:
bash
npm run typecheck
如果项目还没有这个命令,至少直接跑:
bash
npx tsc --noEmit
路由升级不是只看首页能打开就完事。loader、action、params、useLoaderData 相关类型都要过一遍。
5. 只测了构建,没测真实页面
npm run build 过了,不代表路由行为没问题。
至少再跑:
bash
npm run preview
然后手动打开几个关键路径:
txt
/
/login
/settings
/profile/123
随便输一个不存在的地址,看 404 或错误页是否正常
如果项目有权限路由、动态路由、懒加载页面,这些路径都要点一遍。React Router 的问题经常不是首页暴露,而是在深层页面刷新、重定向、参数解析时暴露。
一份可以直接复制的迁移清单
我会按这个顺序来做:
md
## React Router v8 迁移清单
- [ ] 新建迁移分支
- [ ] 确认 Node.js >= 22.12
- [ ] 确认 React / React DOM >= 19.2
- [ ] 确认 Vite >= 7
- [ ] 删除 react-router-dom
- [ ] 升级 react-router 到最新版
- [ ] Framework Mode 项目同步升级 @react-router/* 包
- [ ] 把 react-router-dom 导入迁到 react-router 或 react-router/dom
- [ ] 搜索并清理 @remix-run/* 残留导入
- [ ] 跑 npm run typecheck
- [ ] 跑 npm run build
- [ ] 跑 npm run preview
- [ ] 手动检查首页、登录页、动态路由、404、重定向
- [ ] 同步 CI / Docker / 部署平台 Node 版本
如果你准备用 AI Agent 辅助迁移,也可以把这份清单直接塞给它。不要只说"帮我升级到 React Router v8",任务太宽了。更好的写法是:
txt
请把当前项目从 react-router-dom 迁移到 React Router v8:
1. 先检查 package.json 和路由入口;
2. 删除 react-router-dom,统一使用 react-router;
3. DOM Router 从 react-router/dom 导入;
4. 不要改无关业务逻辑;
5. 改完后运行 typecheck 和 build;
6. 汇报改了哪些文件、哪些命令通过、哪些地方没有验证。
这类迁移很适合交给 Agent 做第一轮机械替换,但最终还是要人来确认路由行为。尤其是登录态、权限跳转、错误页和动态参数,最好自己点一遍。
我的升级建议
如果是新项目,可以直接按 React Router v8 的基线来建:Node 22、React 19、Vite 7+,别再引入 react-router-dom。
如果是老项目,我会分三种情况:
- 已经是 React Router v7:可以开分支试升 v8,重点改导入和环境版本;
- 还在 React Router v6:先升 v7,清 deprecated,再升 v8;
- 还在 Remix v2:先按官方迁移路线迁到 React Router,再考虑 v8。
别低估环境成本。路由代码可能半小时就改完,Node、React、Vite、CI、Docker、部署平台要一起对齐,这才是老项目真正花时间的地方。
资源链接
React Router v8 发布文章:remix.run/blog/react-...
React Router v7 到 v8 升级文档:reactrouter.com/upgrading/v...
React Router Changelog:reactrouter.com/changelog
React Router 文档:reactrouter.com/
结尾
React Router v8 不要求你重写路由,但它已经把旧包、旧运行时和旧框架入口往外推了一步;早点把项目升到新基线,后面接 React 19、RSC 和 Framework Mode 会轻松很多。
你现在项目里用的是 React Router v6、v7,还是还停在更早的版本?