React Router v8 来了:react-router-dom 没了,老项目该怎么迁移?

很多 React 老项目里,路由代码还在从 react-router-dom 里导 BrowserRouterLinkRoutesuseNavigate

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-routerreact-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,用 createBrowserRouterRouterProvider,路由里会有 loaderaction

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,比如 BrowserRouterHashRouter,从 react-router/dom 导。

如果项目用的是 createBrowserRouterRouterProvider,也类似:

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>
  );
}

这个例子看起来改动很小,但真实项目里经常散落着几十个 LinkNavLinkuseParamsuseLocationNavigate。所以迁移时不要凭感觉改,先全局搜,再逐个替换。


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,但 reactreact-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

路由升级不是只看首页能打开就完事。loaderactionparamsuseLoaderData 相关类型都要过一遍。

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,还是还停在更早的版本?

相关推荐
闪闪发光得欧3 小时前
前端提效新思路:Gemini 3.5 自动化定位 CSS 异常
前端·css
yingyima3 小时前
掌握正则表达式的核心:贪婪与非贪婪匹配的底层机制
前端
奇奇怪怪的3 小时前
文档摄入与 Chunking 策略全对决
前端
阳火锅4 小时前
😭测试小姐姐终于不骂我了!这个提BUG神器太香了...
前端·javascript·面试
道友可好4 小时前
AI 是最好的混乱放大器:代码熵管理实战
前端·人工智能·后端
猩猩程序员5 小时前
前端学习 AI Agent 开发
前端
Younglina6 小时前
打了3年羽毛球球才发现:我对自己的装备和胜率一无所知
前端·后端
风骏时光牛马6 小时前
Bash脚本高阶实战与常见报错完整代码案例详解
前端
kartjim6 小时前
我用 AI 一小时写了一个世界杯数据可视化平台|前端 VibeCoding 初体验
前端·程序员·ai编程