React Router v8 升了之后,我花了两天才把项目跑起来

作者:Flynt日期:2026/8/30

昨天看到 React Router 发了 v8.3.1,想着项目还停在 v7 的某个中间版本,趁周末没排需求,不如先升了试试。 changelog 写得很乐观:"all of them are changes you can make in v7",意思是所有破坏性变更都能提前通过 future flags 适配。我当时觉得挺合理的——我们项目 v7 上了快半年,该有的 flag 应该都开了。 结果 pnpm i react-router@latest 装完一跑,直接炸了三个地方。 升级本身不难,但官方文档那句"boring release"有点误导人。对于一个 npm 周下载量 5000 万+ 的包来说,"无聊"这个词容易让人放松警惕。

react-router-dom 没了,换包名

第一个报错就是它。react-router-dom 在 v8 里彻底删了。 项目里所有 from "react-router-dom" 的导入全部标红。好在改动是纯机械性的:

1// v7
2import { BrowserRouter, Routes, Route, Link, useNavigate } from "react-router-dom";
3
4// v8
5import { BrowserRouter, Routes, Route, Link, useNavigate } from "react-router";
6

如果是 DOM 特有的 API(比如 <ScrollRestoration />),需要从 react-router/dom 导入:

1import { ScrollRestoration } from "react-router/dom";
2

这一步我写了个脚本全局替换,大概改了 40 多个文件,花了不到十分钟。 但这里有个坑。我们有个第三方 UI 库内部还在用 react-router-dom,直接报 Module not found。查了一圈才发现它的 peerDependencies 没更新。最后只能手动在 package.json 里加了个 alias 把它重定向到 react-router,算是个临时方案。

1// vite.config.ts 里加 resolve alias
2{
3  "resolve": {
4    "alias": {
5      "react-router-dom": "react-router"
6    }
7  }
8}
9

如果你们项目也依赖了其他还在用 react-router-dom 的包,记得提前检查一下。

中间件——这才是真正的硬骨头

包名替换虽然报错,但至少报错信息清晰,改完就行。中间件这个问题是那种"项目能跑,但行为不对"的类型,排查起来最费时间。 v8 里 v8_middleware 这个 future flag 被移除了,中间件变成了默认行为。我们项目在 v7 的时候没开这个 flag,所以从来没有配置过中间件。 升级后第一次访问受保护页面,直接跳到了登录页——不是我期望的行为。

排了半天才搞清楚。v8 默认启用了中间件管道,而我们的鉴权逻辑之前写在 loader 里。loader 在中间件管道之前执行了一次未鉴权的路由加载,中间件又做了一次鉴权判断,两个逻辑打架了。loader 拿到的是空 token,中间件拦截后又 redirect,结果就是页面先闪一下再跳走。 解决方案是把鉴权从 loader 里拆出来,放到中间件层:

1// app/middleware/auth.ts
2import type { Middleware } from "react-router";
3
4export const authMiddleware: Middleware = async ({ request, context }) => {
5  const url = new URL(request.url);
6
7  // 白名单路由直接放行
8  const publicPaths = ["/login", "/register", "/public"];
9  if (publicPaths.some(p => url.pathname.startsWith(p))) {
10    return;
11  }
12
13  const token = request.headers.get("Authorization")?.replace("Bearer ", "");
14
15  if (!token) {
16    throw new Response(null, {
17      status: 302,
18      headers: { Location: "/login" },
19    });
20  }
21
22  // 验证 token 并存入 context,后续 loader 可以直接用
23  try {
24    const user = await verifyToken(token);
25    context.set("user", user);
26  } catch {
27    throw new Response(null, {
28      status: 302,
29      headers: { Location: "/login" },
30    });
31  }
32};
33

然后在路由配置里注册:

1// app/routes.ts
2import { authMiddleware } from "./middleware/auth";
3
4export const routes = [
5  {
6    path: "/",
7    middleware: [authMiddleware],
8    children: [
9      { index: true, file: "./routes/dashboard.tsx" },
10      { path: "settings", file: "./routes/settings.tsx" },
11      // ...
12    ],
13  },
14  { path: "/login", file: "./routes/login.tsx" },
15];
16

loader 里就干净了,直接从 context 拿用户信息:

1// routes/dashboard.tsx
2export async function loader({ context }: LoaderFunctionArgs) {
3  const user = context.get("user");
4  return { userName: user.name, role: user.role };
5}
6

改完之后我松了口气,在本地多点了几个页面,都没问题。心想这升级也没网上说的那么恐怖嘛。 然后部署到测试环境,CI 挂了。

Node 版本问题:CI 还停在 20

v8 要求 Node 22.22.0+,React 19.2.7+,Vite 7+。 我们的 CI 跑的是 Node 20 LTS。本地开发环境倒是已经切到 22 了,但 CI 配置文件写死了版本。 这个改动本身不大,就是改一下 GitHub Actions 的 workflow:

1# .github/workflows/ci.yml
2- uses: actions/setup-node@v4
3  with:
4    node-version: "22.22"  #  "20" 改成 "22.22"
5    cache: "pnpm"
6

改完之后还有第二个问题——Docker 基础镜像。我们的测试容器用的是 node:20-slim,也得换。换成 node:22-slim 之后,跑了一轮测试全过了。 不过有个细节值得注意。Node 22 里有个 --experimental-webstorage 默认行为变了,我们有一个依赖 localStorage 的工具库在测试环境里直接报错。后来加了 NODE_OPTIONS=--no-experimental-webstorage 才绕过去。如果你们的测试用例里有 mock localStoragesessionStorage 的逻辑,升 Node 22 后记得跑一遍看看。 到这里其实技术上的问题已经解决了,但还有个更隐蔽的坑。 我们有个预渲染的静态页面,用了 v8_passThroughRequests flag。v8 里这个 flag 被移除并变成了默认行为。理论上应该无缝衔接,但实际跑预渲染时,部分页面的 loader 数据没有被正确序列化到 HTML 里。 查了 v8.3.0 的 changelog 才发现这是个已知问题,在 v8.3.1(就是昨天刚发的版本)里修了。所以如果你也碰到预渲染相关的问题,确保升级到 v8.3.1+。

ESM-only:我们没踩坑,但看到不少人踩了

我们项目全用 Vite,两年前就全面 ESM 了,所以这步基本没遇到问题。 但 v8 的 ESM-only 对老项目杀伤力很大。tsconfig 的 targetlib 强制 ES2022,还在用 require() 或者 jest.config.js 写 CommonJS 语法的项目,会直接跑不起来。掘金社区好几个人吐槽这步卡了一整天,特别是 webpack 4 + Jest 27 的老组合。 如果你属于这种情况,建议先把构建工具链升级到位——Webpack 5+ 或 Vite 7+,Jest 29+ 或换 Vitest——再动 React Router。这个前置工作量和升 v8 本身差不多,但做完之后后续升级就顺了。

还有一些零散的坑

splitRouteModules 在 v8 里变成了顶层配置且默认开启。我们项目路由模块本来就很小,没感受到明显变化。但如果你发现升级后构建产物变大了或者代码分割行为和之前不一样,查一下这个配置。 有个同事的项目遇到了 data 参数废弃的问题。v8 把 loader 的 data 参数改成了 loaderData,meta API 里也得跟着改。他的项目里有个自定义的 useRouteData hook 直接引用了旧的 data 字段,全局搜了十几处才改完。react-router/dom 里有些 API 的导入路径也变了。如果项目里用了 <ScrollRestoration> 或者 <Form>,确保是从 react-router/dom 而不是 react-router 导入。这个改动不大,但 IDE 不会自动提示,得自己一个个检查。

另外 v6 和 Remix v2 正式 EOL 了,不再有安全更新。如果项目还在这两个版本上,这次升级就不是"想不想升"的问题,是"必须升"。React Router 官方明确说了,后续安全补丁只会打到 v7 和 v8 上。 还有个事值得一提:部分开发者已经开始迁移到 TanStack Router 了。v8 发布后 Reddit 上有人发帖吐槽 breaking changes,评论区不少人说正好趁机换。TanStack Router 的端到端类型安全和内置的 stale-while-revalidate 缓存确实不错,但如果你项目已经深度使用 React Router 的 loader/action 模式,换路由库的成本比升版本高太多了。

花了多久

换包名 + 修导入路径:半小时。中间件改造 + 鉴权逻辑迁移:大半天。CI Docker 镜像 + Node 版本:1 小时。预渲染问题排查(发现是 v8.3.1 修的):半天。回归测试 + 手动点页面:2 小时。 两天,比我预期的半天长得多。

回头看官方那句"it's not a major version if nothing broke",说得确实没错。但对一个有真实业务逻辑的项目来说,"没 break"和"改起来很顺滑"是两回事。中间件的迁移不算复杂,但它要求你重新思考鉴权逻辑放在哪一层,这种架构层面的调整本来就不可能半小时搞定。

如果你们项目也准备升,几个建议:

先在本地跑一遍 pnpm i react-router@latest,看报错有多少。如果只是 import 路径的问题,半天能搞定。如果涉及中间件和 ESM 兼容,预留两天。另外就是直接上 v8.3.1,别停在之前的版本,预渲染的 bug 刚修。


React Router v8 升了之后,我花了两天才把项目跑起来》 是转载文章,点击查看原文


相关推荐


两个 Agent 怎么协作:Host-Worker 模式(第91篇-E77)
leeyi2026/8/22

Part 16 开篇。前面 15 个 Part 讲的是一个 Agent 怎么做安全、怎么加记忆、怎么接知识库——但单个 Agent 的注意力是有限的。一个 Agent 面对"帮我查北京天气、订机票、比价酒店"这种多维度任务,会在上下文里来回跳,工具一多就乱。 这篇回答一个问题:两个 Agent 怎么协作——Host-Worker 模式,主 Agent 分派、子 Agent 执行,注意力该拆给谁。 两个问题串全篇: Eino 的 Host MultiAgent 怎么让 host LLM 把 s


漫话火山 Milvus|为 Agent、RAG 、语义搜索而生,AI 时代的极致性价比之选
火山引擎Agent社区2026/8/8

托管锁定、自建难扩容、性能和成本难以兼顾? 火山 Milvus,开源兼容、可自由迁移;自研算法优化算力开销,支持 Serverless;联动火山大模型生态,适配多模态 RAG 与 Agent,可控、性能、性价比一站式解决。 火山 Milvus,基于开源 Milvus 构建的全托管数据库服务,提供高效的非结构化数据检索能力,适用于多样化 AI 场景,客户无需再关心底层硬件资源,降低使用成本,提高整体效率。


CSS 现代布局终极方案:Grid 完整实战,替代浮动/弹性布局复杂场景
前端人类学2026/7/30

Hi,我是前端人类学! 从“圣杯”到“卡片墙”,Grid 正在重新定义我们对 Web 布局的想象。 如果你还在用 float 清浮动、用 margin 推间距、用 flex 小心翼翼地嵌套多行网格,那今天这篇文章,值得你多看两眼。 CSS Grid 不是来“卷”谁的,它是来解决问题的——那些你以前觉得“凑合能看但维护起来想删库”的布局问题。 文章目录 一、Grid 凭什么说自己是“终极方案”?二、干掉浮动和复杂 Flex:两个核心实战模式2.1 响应式卡片网格:一行代码,无需媒


算力赋能零售与创意新生态:视程空间Pandora,解锁线下场景智能化无限可能
视***间2026/7/22

智慧零售与创意开发正成为驱动消费升级与产业创新的核心引擎。从线下门店的智能导购、客流分析,到沉浸式体验场景的交互设计、创意内容生成,传统模式已难以满足高效、个性化、安全合规的发展需求。视程空间 Pandora 系列边缘算力盒子,凭借 NVIDIA Jetson Orin™ NX/Nano Super 的强劲算力、极致集成设计与全开放生态,成为智慧零售场景落地与创意开发实践的绝佳解决方案,为企业提供安全、高效、可定制的边缘 AI 底座,让零售更智能、让创意更自由。 一、智慧零售:从流量运营到体


基于 OpenClaw 构建医疗健康系统:智能问诊与用药管理的全链路实战
七夜zippoe2026/7/14

摘要:本文基于 OpenClaw v2.4 与 Python 3.11,以"医疗健康系统"为场景,完整演示如何用对话式 AI 框架落地智能问诊、健康监测、用药管理与电子病历四大模块。文章先拆解 OpenClaw 的核心抽象与医疗场景诉求,再用 Mermaid 图与可运行 Python 代码逐模块实现,并给出运行验证、阈值配置与合规边界。面向中高级后端与全栈开发者,无需医学背景即可照着落地;重点讲清"领域建模 + 合规护栏"的工程取舍,而非堆砌大模型。读完可掌握一套可复用的行业应用构建范式。


计算机导论_第4章_笔记
薄荷椰果抹茶2026/7/6

内容提要 程序设计语言翻译系统操作系统工具软件 第1节 程序设计语言翻译系统 1.1 概述 计算机硬件只能识别并执行机器指令,但人们普遍习惯于使用高级程序设计语言或汇编语言来编写程序。为了让计算机能够理解高级程序设计语言或汇编语言并执行用它编写的程序,必须要为它配备一个"翻译",这就是程序设计语言翻译系统。 定义:程序设计语言翻译系统是一类系统软件,它能够将使用某一种源语言编写的程序翻译成为与其等价的使用另一种目标语言编写的程序。 源程序:使用源语言编写的程序目标程序:使用目标语言编写的程序


图解 MongoDB 18|复制集拓扑:Primary、Secondary 和 Arbiter 的分工
十三Tech2026/6/28

存储引擎阶段回答了「数据怎么存、怎么不丢」,但留下一个致命问题:单机宕机了怎么办。一台 mongod 进程挂掉,不管是硬件故障、进程崩溃还是网络隔离,所有读写都会中断。这在生产环境是不可接受的。 MongoDB 解决单点故障的方式是复制集(Replica Set)——把数据复制到多个节点,一台挂了自动切换到另一台。这一阶段(18–23)就围绕复制集展开,从拓扑结构、复制原理、延迟、选举到读写关注,把 MongoDB 的高可用讲透。 先把机制边界说清楚 复制集是一组维护相同数据集的 mongod


百度 C++/PHP 研发一二面:一面扫八股和算法,二面开始逼近 Redis、MySQL 和秒杀设计
TechPioneer_lp2026/6/19

这篇百度 C++/PHP 研发面经很适合作为“后端基础到系统设计过渡”的样本来看。 它的一面还比较像常规校招: 算法 OS 计网 HTTP Redis 静态 / 动态链接 C++ 基础 但二面明显开始往: 智能指针 TCP 窗口和拥塞 Redis 各种底层结构 MySQL 索引、隔离级别、锁 秒杀系统和高并发设计 去推进了。 校招大礼包获取:入口 可能是至今最全,最好,最实用的校招大礼包,减少信息差,预期漫步无敌的刷提,不如有的放矢,针对性的准备,这


从单机到分布式:用 Go + Eino + DeepSeek V4 构建生产级 Code Review Agent
银河技术2026/6/11

从单机到分布式:用 Go + Eino + DeepSeek V4 构建生产级 Code Review Agent 不是把大模型接到 GitHub Webhook 上,就叫生产级 Code Review Agent。真正决定系统上限的,是任务编排、规则前置、上下文治理、并发隔离与可观测性。 引言:为什么团队越来越需要“生产级” Code Review Agent 在小团队里,Code Review 通常是一个“人盯人”的流程:开发者提 PR,Reviewer 看 diff,提几点


Webpack如何实现万物皆可import?loader的使用/配置/手写实践
漂流瓶jz2026/6/4

Webpack是前端历史上具有统治地位的打包工具,应用非常广泛。虽然现在逐渐被性能更强的工具替代,但是依然有很多工程使用。loader是Webpack中的一种重要的外部插入配置工具,负责对源代码进行转换。Webpack本身只能理解JavaScript和JSON文件,其它类型的文件不能处理。正是使用各种loader,Webpack才有了将各种格式的资源和代码识别和引入的能力。当然,loader的能力也并不仅限于此。 loader使用示例 为了了解loader的作用和使用方式,我们举例一些现有的知名

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读