首页
/ React Router v7 到 v8 的发布演进全解析:基于官方 CHANGELOG 的升级与特性速览

React Router v7 到 v8 的发布演进全解析:基于官方 CHANGELOG 的升级与特性速览

2026-09-05 15:23:38作者:郜逊炳

本文以仓库根目录的 CHANGELOG.md 为绝对主体,系统梳理 React Router 自 v7.0.0(2024-11-21)至 v8.3.0(2026-07-22)的完整发布脉络:包括每个版本的重大变更、破坏性改动、安全修复与代码示例,并结合同仓库的 API 开发策略、changeset 发布脚本与包版本信息,帮助你判断当前 v7 应用距离 v8 还差哪些 Future Flag 迁移,以及如何在升级时安全地消费这些发布说明。

一、如何阅读 React Router 的 CHANGELOG

CHANGELOG.md 开头明确了该文件的定位:它只覆盖 v7.0.0 及以后的所有版本;v6.x 版本需要查看 v6 分支的 CHANGELOG,更早的版本则需要去 GitHub Releases 页面查阅。维护者刻意把发布说明集中在这个文件里而非分页的 Releases 页面,原因有二:

  1. GitHub 的 UI 分页使得跨越大范围版本检索发布说明变得困难;
  2. 分页列表视图会截断较长的发布说明,必须点击进入详情才能看到全文。

当前仓库中 react-router 包 的版本号为 8.3.0,与该 CHANGELOG 中最新条目 v8.3.0 一致,说明本仓库即为 v8.3.0 的发布快照。

每个版本条目的固定结构

CHANGELOG 中每个版本条目由若干标准小节组成,阅读升级说明时应重点关注对应小节:

  • What's Changed:该版本最值得注意的特性或行为变化(通常附带完整代码示例与迁移指南);
  • Minor Changes / Patch Changes:按 SemVer 语义组织的稳定 API 变更;
  • Unstable Changes:以 ⚠️ Unstable features are not recommended for production use 标注,对应 unstable_* 前缀的实验性 API;
  • Security Notice:安全漏洞修复说明(列出对应的 Security Advisory);
  • Full Changelog:该版本与上一版本的完整对比范围(如 v8.2.0...v8.3.0)。

这种“unstable 先行、minor 稳定、major 收编”的节奏并非随意,它直接来自项目的 API 开发策略:破坏性改动先以 Future Flag 形式在 minor 版本引入;仍在设计中的功能则以 Unstable Flag 形式发布(可能随时变更、被移除,且不保证升级路径),因此不稳定 API 直接随 SemVer patch 版本发布;当 unstable flag 稳定为 Future Flag 时,才会以 minor 版本发布并进入 Future Changes Guide。换言之,CHANGELOG 就是 unstable flag 的唯一权威来源——官方文档明确指出“要了解当前 unstable flag,请持续关注 CHANGELOG”。

发布说明是如何生成的

从源码结构看,这个 CHANGELOG 是由仓库内置的 changeset 工作流驱动的。根 package.json 中定义了一组 changes:* 脚本,对应 scripts/changes/ 目录下的实现:

命令 脚本 作用
pnpm changes:add add.ts 为一次改动添加 changeset 文件(落入 .changes/ 目录)
pnpm changes:validate validate.ts 校验待发布 changeset
pnpm changes:version version.ts 依据 changeset 计算并写入各包版本
pnpm changes:pr pr.ts 生成版本提升 PR
pnpm changes:publish publish.ts 发布并生成发布说明

也就是说,你在 CHANGELOG 中看到的每一条“package - 描述 + PR 编号”记录,都源自贡献者合并前写入 .changes/ 的 changeset,发布流程会将其聚合进各包的 CHANGELOG 并汇总到根 CHANGELOG。这解释了为什么 v7.8.0 及更早的条目末尾带有“Changes by Package”分包链接(指向各 packages/*/CHANGELOG.md),而较新的版本已改为直接在根文件中逐包列出。

二、v7.0.0:大一统的起点(2024-11-21)

v7 是整条时间线的地基,其“Breaking Changes”小节值得完整继承。

包结构重构

  • react-router-dom@remix-run/react@remix-run/server-runtime@remix-run/router 全部合并进 react-router 一个包;为了平滑迁移,v7 仍发布 react-router-dom 作为 react-router 的 re-export 包(这个包在 v8 中被彻底移除,见下文)。
  • @remix-run/cloudflare-pages@remix-run/cloudflare-workers 合并为 @react-router/cloudflare
  • react-router-dom-v5-compatreact-router-native 自 v7 起移除。

移除适配层 re-exports

Remix v2 时代各运行时包(node/cloudflare/deno)会 re-export 所有 @remix-run/server-runtime 常用 API;v7 不再这样做。通用 API 一律从 react-router 导入,只有运行时特定 API 从各运行时包导入:

// Runtime-specific APIs
import { createFileSessionStorage } from "@react-router/node";
// Runtime-agnostic APIs
import { redirect, useLoaderData } from "react-router";

移除的 API

jsondeferunstable_composeUploadHandlersunstable_createMemoryUploadHandlerunstable_parseMultipartFormData 在 v7 中全部移除(defer 的实现被单 fetch + turbo-stream 的原生 Promise 流取代,json 可用 Response.json 替代)。

最低版本要求

  • node@20,且不再提供 installGlobals 来为 fetch 做 polyfill;
  • react@18react-dom@18

收编的 Future Flag 行为

v6 的 future.v7_relativeSplatPathfuture.v7_startTransitionfuture.v7_fetcherPersistfuture.v7_normalizeFormMethodfuture.v7_partialHydrationfuture.v7_skipActionStatusRevalidation,以及 Remix v2 的 future.v3_fetcherPersistfuture.v3_relativeSplatPathfuture.v3_throwAbortReasonfuture.v3_singleFetchfuture.v3_lazyRouteDiscoveryfuture.v3_optimizeDeps,全部成为 v7 默认行为。

Vite 成为唯一编译器

Remix 的 Vite 插件成为构建全栈 SSR 应用的标准方式,旧的 esbuild 编译器不再可用。对 Remix 用户的两处关键改名与移除:

-import {
-  vitePlugin as remix,
-  cloudflareDevProxyVitePlugin,
-} from "@remix/dev";

+import { reactRouter } from "@react-router/dev/vite";
+import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";

Vite 插件的 manifest 选项被移除,被功能更强的 buildEnd hook 取代(它会收到 buildManifest 参数):

// react-router.config.ts
import { type Config } from "@react-router/dev/config";
import { writeFile } from "node:fs/promises";

export default {
  async buildEnd({ buildManifest }) {
    await writeFile(
      "build/manifest.json",
      JSON.stringify(buildManifest, null, 2),
      "utf-8"
    );
  },
} satisfies Config;

暴露 Router Promise

借 React 19 在渲染阶段处理 Promise 的一等支持(React.use/useAction),以下 API 开始返回 Promise 而非 undefineduseNavigate()useSubmit()useFetcher().loaduseFetcher().submituseRevalidator().revalidate()

routes.ts 与类型安全

Framework 模式下路由定义于 app/routes.ts,通过 RouteConfig 类型导出,提供 routeindexlayout 辅助函数:

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

export const routes: RouteConfig = [
  index("./home.tsx"),
  route("about", "./about.tsx"),

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

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

Remix 用户可用 @react-router/fs-routes 保持文件系统路由:export const routes: RouteConfig = flatRoutes();,也可以把 flatRoutes({ rootDirectory: "fs-routes" }) 的结果展开混入配置式路由数组;对于旧的 Remix routes option,则可用 @react-router/remix-routes-option-adapter 适配。

同时 v7 引入了路由模块类型生成:为每个路由模块生成类型,路由组件导出接收类型化 props,可从 ./+types/<route 文件名> 导入这些类型。

Prerendering

v7 引入 prerender 配置支持 SSG:构建时预渲染 .html.data 文件,运行时由服务器或 CDN 静态提供:

export default defineConfig({
  plugins: [
    reactRouter({
      async prerender({ getStaticPaths }) {
        let slugs = await fakeGetSlugsFromCms();
        return [
          ...getStaticPaths(),
          ...slugs.map((slug) => `/product/${slug}`),
        ];
      },
    }),
    tsconfigPaths(),
  ],
});

三、v7.x 的特性演进主线(v7.1.0 – v7.18.0)

v7 小版本几乎每个 minor 都有标志性特性,以下按 CHANGELOG 原文脉络逐条展开。

类型安全 href 工具(v7.2.0,2025-02-18)

Framework 模式新增完全类型安全的 href,链接路径与参数都获得自动补全和类型校验:

import { href } from "react-router";

export default function Component() {
  const link = href("/blog/:slug", { slug: "my-first-post" });
  //                ^ type-safe!     ^ Also type-safe!

  return (
    <main>
      <Link to={href("/products/:id", { id: "asdf" })} />
      <NavLink to={href("/:lang?/about", { lang: "en" })} />
    </main>
  );
}

传入非法路径或非法参数会直接得到类型错误:href("/not/a/valid/path")href("/blog/:slug", { oops: "bad param" }) 均编译报错。后续版本持续打磨 href 的类型细节:7.6.1 修复可选静态段展开(/user/:id? 展开为 /user/user/:id),7.6.2 修复可选动态参数回退(href("/users/:id?", params) 恢复可用且 IDE 会给出路径建议),7.6.1 起 splat 也能正确替换(href("/products/*", { "*": "/1/edit" })/products/1/edit)。

同一版本还包含预渲染 + SPA 回退三种形态的规则:

  • ssr:false 且无 prerender 配置 → 纯 SPA Mode,index.html 只渲染到 root 路由,可水合任意合法路径;
  • ssr:false + prerender 配置但不包含 / → 仍生成可水合任意路径的 SPA Mode index.html
  • ssr:false + prerender 配置包含 /index.html 专用于 root index 路由,另生成 __spa-fallback.html 服务未预渲染路径。

此外 SPA Mode 解除了 root loader 禁令(root 在构建时必然渲染),Route.HydrateFallbackProps 因此新增可选 loaderData prop:当 HydrateFallback 因子路由加载而渲染时它有值,因本路由自身 clientLoader 水合而渲染时为 undefined

Split Route Modules(v7.2.0 unstable → v7.10.0 稳定 → v8.0.0 默认)

这是 CHANGELOG 中篇幅最长的特性之一。Route Module API 把路由所需的一切都放在一个文件里,但在 clientLoader/clientAction/HydrateFallback 场景下有性能代价:

import { MassiveComponent } from "~/components";

export async function clientLoader() {
  return await fetch("https://example.com/api").then((response) =>
    response.json(),
  );
}

export default function Component({ loaderData }) {
  return <MassiveComponent data={loaderData} />;
}

未拆分时,客户端导航必须下载整个路由模块后才能开始执行 clientLoader

Get Route Module:  |--=======|
Run clientLoader:            |-----|
Render:                            |-|

开启拆分后,路由模块在生产构建期被拆成多个更小的(虚拟)模块,clientLoaderComponent 可并行下载:

Get clientLoader:  |--|
Get Component:     |=======|
Run clientLoader:     |-----|
Render:                     |-|

对应的产物是两个虚拟模块:routes/example.tsx?route-chunk=clientLoader(仅含 clientLoader)与 routes/example.tsx?route-chunk=main(含组件及其依赖)。

关键限制:只有当被拆分的导出不共享同文件内的代码时才能拆分。例如 clientLoaderComponent 都调用同文件的 shared 函数时,该模块会被降级为单块;解决办法是把共享代码抽到独立文件(如 routes/example/shared.tsx)再分别 import。性能敏感的项目可设置为 "enforce"

export default {
  future: {
    unstable_splitRouteModules: "enforce",
  },
};

此时任何无法拆分的路由模块都会构建报错:

Error splitting route module: routes/example/route.tsx

- clientLoader

This export could not be split into its own chunk because it shares code with other exports. You should extract any shared code into its own module and then import it within the route module.

后续演进:v7.10.0 将 future.unstable_splitRouteModules 稳定为 future.v8_splitRouteModules;v8.0.0 则把它提升为顶层 splitRouteModules 配置且默认启用splitRouteModules: false 可保持单块,"enforce" 强制所有路由可拆分)。

Middleware 与 Context(v7.3.0 unstable → v7.9.0 稳定 → v8.0.0 默认)

v7.3.0 在 future.unstable_middleware 旗标后引入中间件(设计文档见 decisions/0014-context-middleware.md)。开启方式需要同时启用运行时旗标与类型(v7.6.0 之后类型自动启用,见下文):

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

declare module "react-router" {
  interface Future {
    unstable_middleware: true; // 👈 Enable middleware types
  }
}

export default {
  future: {
    unstable_middleware: true, // 👈 Enable middleware
  },
} satisfies Config;

路由上按序声明中间件数组,服务端/客户端分别用不同导出:

// Framework mode
export const unstable_middleware = [serverLogger, serverAuth]; // server
export const unstable_clientMiddleware = [clientLogger]; // client

// Library mode
const routes = [
  {
    path: "/",
    // Middlewares are client-side for library mode SPA's
    unstable_middleware: [clientLogger, clientAuth],
    loader: rootLoader,
    Component: Root,
  },
];

客户端中间件没有“响应”概念,next() 不返回任何东西(数据全部由有状态的 router 在幕后处理):

const clientLogger: Route.unstable_ClientMiddlewareFunction = async (
  { request },
  next,
) => {
  let start = performance.now();

  // Run the remaining middlewares and all route loaders
  await next();

  let duration = performance.now() - start;
  console.log(`Navigated to ${request.url} (${duration}ms)`);
};

服务端中间件的 next() 返回即将发出的 HTTP Response,可以改造后返回,也可以直接 throw 新响应短路流程:

const serverLogger: Route.unstable_MiddlewareFunction = async (
  { request, params, context },
  next,
) => {
  let start = performance.now();
  let res = await next(); // 👇 Grab the response here
  let duration = performance.now() - start;
  console.log(`Navigated to ${request.url} (${duration}ms)`);
  return res; // 👇 And return it here (optional)
};

鉴权短路示例(无后处理时甚至不必调用 next):

import { sessionContext } from "../context";
const serverAuth: Route.unstable_MiddlewareFunction = (
  { request, params, context },
  next,
) => {
  let session = context.get(sessionContext);
  let user = session.get("user");
  if (!user) {
    session.set("returnTo", request.url);
    throw redirect("/login", 302);
  }
};

“404 时查 CMS 重定向”这种后置逻辑:

const redirects: Route.unstable_MiddlewareFunction = async ({
  request,
  next,
}) => {
  let res = await next();
  if (res.status === 404) {
    let cmsRedirect = await checkCMSRedirects(request.url);
    if (cmsRedirect) {
      throw redirect(cmsRedirect, 302);
    }
  }
  return res;
};

启用 middleware 后,loader/actioncontext 参数从 AppLoadContext 变为 ContextProvider 实例,配合类型安全的 unstable_createContext(类似 React.createContext)使用:

import { unstable_createContext } from "react-router";
import { Route } from "./+types/root";
import type { Session } from "./sessions.server";
import { getSession } from "./sessions.server";

let sessionContext = unstable_createContext<Session>();

const sessionMiddleware: Route.unstable_MiddlewareFunction = ({
  context,
  request,
}) => {
  let session = await getSession(request);
  context.set(sessionContext, session);
  //                          ^ must be of type Session
};

// ... then in some downstream loader
export function loader({ context }: Route.LoaderArgs) {
  let session = context.get(sessionContext);
  let profile = await getProfile(session.get("userId"));
  return { profile };
}

自定义服务器的 getLoadContext 相应改为返回 unstable_InitialContext(即 Map<RouterContext, unknown>):

let adapterContext = unstable_createContext<MyAdapterContext>();

function getLoadContext(req, res): unstable_InitialContext {
  let map = new Map();
  map.set(adapterContext, getAdapterContext(req));
  return map;
}

客户端侧同样获得 contextclientLoader/clientAction 在客户端也会收到 unstable_RouterContextProvider 实例,每次导航(或 fetcher 调用)创建全新实例;可在 createBrowserRouter(routes, { unstable_getContext })<HydratedRouter unstable_getContext> 处提供初始值。

稳定化轨迹:v7.8.0 大幅打磨中间件 API(next 不再抛错、错误冒泡到正确 ErrorBoundarygetLoadContext 签名改为返回 RouterContextProvider 实例而非 MapstaticHandlerunstable_respond 重命名为 unstable_generateMiddlewareResponse 且改为回调内执行 query/queryRoute);v7.9.0 正式移除 unstable_ 前缀,RouterContextProvidercreateContextcreateBrowserRoutergetContext<HydratedRouter getContext> 进入稳定 API(对应 docs/how-to/middleware.md);v8.0.0 移除 future.v8_middleware 旗标——middleware 永远启用,context 永远是 RouterContextProvider 实例,MiddlewareEnabled 类型与 Future 模块增强模式随之删除。

route.lazy 对象 API(v7.5.0)

函数式 route.lazy() 无法对路由属性做细粒度懒加载,v7.5.0 引入对象式 API:

createBrowserRouter([
  {
    path: "/show/:showId",
    lazy: {
      loader: async () => (await import("./show.loader.js")).loader,
      action: async () => (await import("./show.action.js")).action,
      Component: async () => (await import("./show.component.js")).Component,
    },
  },
]);

v7.5.1 补充:对象式 route.lazy 中,HydrateFallback/hydrateFallbackElement 在水合后的懒加载阶段会被跳过——可以把它们放进独立文件,水合后完全不必下载。

routeDiscovery 配置(v7.6.0)

Lazy Route Discovery(/__manifest 路径的懒路由发现)从此可调可关:

// react-router.config.ts

export default {
  // You can modify the manifest path used:
  routeDiscovery: { mode: "lazy", manifestPath: "/custom-manifest" }

  // Or you can disable this feature entirely and include all routes in the
  // manifest on initial document load:
  // routeDiscovery: { mode: "initial" }

  // If you don't specify anything, the default config is as follows, which enables
  // Lazy Route Discovery and makes manifest requests to the `/__manifest` path:
  // routeDiscovery: { mode: "lazy", manifestPath: "/__manifest" }
} satisfies Config;

Future Flags 的类型自动启用(v7.6.0)

此前启用改变类型语义的 future flag 需要两步:写运行时旗标 + 手动 declare module 增强类型。v7.6.0 起只需运行时旗标,React Router 会把对应 declare module 自动生成到 .react-router/types(实现细节上目前是 .react-router/types/+register.ts,可能变化):

// react-router.config.ts
// Step 1: Enable middleware
export default {
  future: {
    unstable_middleware: true,
  },
};

// No step 2! That's it!

useRoute(v7.9.4 unstable)

Framework 模式下以类型安全方式访问指定路由loaderData/actionData,相当于“更好的 useRouteLoaderData”且支持 actionData。以 admin 路由为例,其所有子路由都能通过一个复用组件拿到数据:

// app/routes/admin.tsx
import { Outlet } from "react-router";

export const loader = () => ({ message: "Hello, loader!" });
export const action = () => ({ count: 1 });

export default function Component() {
  return (
    <div>
      {/* ... */}
      <Outlet />
      {/* ... */}
    </div>
  );
}
import { unstable_useRoute as useRoute } from "react-router";

export function AdminWidget() {
  const admin = useRoute("routes/dmin");
  //                      ^^^^^^^^^^^  传入非法路由 ID 会报 TS 错误
}

路由不在当前页面时返回 undefinedroot 是例外,永不返回 undefined);loaderData/actionData 为可选类型(action 未触发或 loader 抛错时可能不存在)。无参调用 useRoute() 等价于 useLoaderData + useActionData 的合并,但此时 loaderData/actionData 类型为 unknown,需要自行收窄或通过 props 传递类型。v7.9.5 又给 unstable_useRoute 增加了类型安全的 handle 字段。

RSC 支持(v7.7.0 → v7.9.2 → v7.14.0 → v8.x)

  • v7.7.0(2025-07-16):Data Mode 的 unstable RSC API 首次出现——unstable_RSCHydratedRouterunstable_RSCStaticRouterunstable_createCallServerunstable_getRSCStreamunstable_matchRSCServerRequestunstable_routeRSCServerRequest(文档见 docs/how-to/react-server-components.md);
  • v7.9.2(2025-09-24):Framework Mode 的 unstable RSC 支持首次发布,同版还有 fetcher.unstable_reset()
  • v7.14.0:RSC 支持预渲染与 SPA Mode;react-router reveal 支持 RSC Framework Mode 的 entry.client/entry.rsc/entry.ssr<Link prefetch> 在 RSC Framework Mode 可用;路由模块新增互斥的 Server Component 对应导出(ServerComponent/ServerErrorBoundary/ServerLayout/ServerHydrateFallback,附 before/after 迁移示例);
  • v8.3.0:自定义 RSC 入口的更新(客户端版本号、SRI、CSP nonce 接线,见第五节)。

Instrumentation(v7.9.5 unstable → v7.15.0 稳定 → v8.1.0 增强)

v7.9.5 引入 unstable_instrumentations,可对服务端 handler、客户端导航/fetch、loaderaction、middleware、route.lazy 注入运行时观测逻辑;Framework Mode 在 entry.server.tsx 导出、entry.client.tsx 传给 <HydratedRouter>,Data Mode 传 createBrowserRouter(routes, { unstable_instrumentations: [...] })。同版还新增 unstable_pattern 参数(未插值的路由 pattern,如 /blog/:slug),便于按路由聚合日志/指标。

v7.15.0 将其稳定为 instrumentations(配套类型 ServerInstrumentationClientInstrumentation 等去掉 unstable_ 前缀),文档见 docs/how-to/instrumentation.md

v8.1.0 进一步补充观测元数据:对尚未完成路由匹配的外层(handlernavigate),结果中新增 result.metaurl/pattern/params);服务端 handler 还额外提供出站响应 statusCode

export const instrumentations = [
  {
    handler(handler) {
      handler.instrument({
        async request(handleRequest) {
          let result = await handleRequest();

          // Available to server `handler`, and router `navigate`/`fetch` instrumentations
          let normalizedUrl = result.meta?.url;
          let routePattern = result.meta?.pattern;
          let routeParams = result.meta?.params;

          // Available to server `handler` only
          let statusCode = result.statusCode;
        },
      });
    },
  },
];

v7.15.0:面向 v8 的大规模稳定化(2026-05-05)

这批重命名对已采用 unstable 版本的用户是破坏性变更,CHANGELOG 用一张映射表列全:

原 unstable API 稳定后名称
future.unstable_passThroughRequests future.v8_passThroughRequests
future.unstable_subResourceIntegrity 顶层 config.subResourceIntegrity
prerender.unstable_concurrency prerender.concurrency
unstable_url(loader/action/middleware/instrumentation 参数) url
unstable_instrumentations instrumentations
unstable_pattern pattern
unstable_defaultShouldRevalidate defaultShouldRevalidate
unstable_useTransitions useTransitions
unstable_mask<Link>useLinkClickHandleruseNavigateLocation mask

同版还带来路由匹配性能优化:预计算并缓存扁平化/排序后的路由分支,减少关键路径上的 matchRoutes 调用,官方基准显示服务端请求处理提升约 10–15%(客户端侧另一项优化报告约 15–30%)。

其余 v7.x 里程碑速览:

  • v7.4.0:为 virtual:react-router/server-build 虚拟模块生成类型;
  • v7.5.1dataStrategy 新增 unstable_runClientMiddlewareunstable_shouldCallHandler/unstable_shouldRevalidateArgs
  • v7.9.4unstable_getRequest(RSC)等;
  • v7.10.0:稳定 fetcher.reset()DataStrategyMatch.shouldCallHandler()/shouldRevalidateArgsfuture.v8_splitRouteModulesfuture.v8_viteEnvironmentApi;新增 unstable_useTransitions(控制 React.startTransition/React.useOptimistic 的使用,React 19 增强行为需显式置 true);routes.ts 现在在 routes.ts 求值前先加载环境变量(可基于 VITE_ 前缀环境变量动态拼路由);
  • v7.11.0vite preview 支持(预览生产构建)、客户端 onError 稳定(<RouterProvider onError>/<HydratedRouter onError>);
  • v7.12.0future.unstable_trailingSlashAwareDataRequests(尾斜杠数据请求路径一致性,附 document/data 请求 pathname 对照表);
  • v7.13.1:URL Masking——<Link unstable_mask> 让路由导航到 ?image=N 而地址栏显示 /images/N(适合图库弹窗等场景,masked location 可在 useLocation().unstable_mask 读取;仅适用 SPA 场景,SSR 时从 history.state 移除);
  • v7.13.2future.unstable_passThroughRequests + unstable_url 参数(request 原样透传,.data 后缀与 index/_routes 查询参数可见,unstable_url 提供归一化 URL),v7.16.0 稳定为 future.v8_trailingSlashAwareDataRequests,v8.0.0 后成为默认且 url 正式落地;
  • v7.15.1unstable_useRouterState()active/pending 双结构整合 useLocation/useSearchParams/useParams/useMatches/useNavigationType/useNavigation 等十余个 hook,是“Less is More”设计目标的产物,部分旧 hook 可能在未来版本被弃用);
  • v7.18.0(2026-06-16):CSRF 检查逻辑修复——改为直接校验 request URL 中的 host 而非 HTTP 头(头解析是适配层职责)。这是一个“可能是破坏性的 bug 修复”:部署在反向代理后、且适配层未在请求 URL 上设置预期 host 的应用,可能需要把内部 host 加入 allowedActionOrigins;官方建议升级时专门测试 mutation 请求。

安全发布(Security Notice 条目)

CHANGELOG 中带 Security Notice 的版本值得单独建立升级红线意识:

版本 安全修复
v7.4.1 Host/X-Forwarded-Host 头导致的 URL 操纵与缓存污染(端口消毒不足,CVE-2025-31137)
v7.5.2 两个可致缓存投毒的漏洞(SPA Mode/预渲染的构建期专用头被滥用)
v7.9.0 <Meta> 生成 script:ld+json 时的 XSS
v7.9.4 createFileSessionStorage() 使用未签名 cookie 时的未授权文件访问
v7.9.6 不可信路径导致的意外外部重定向
v7.12.0 三个漏洞:Action/Server Action 处理的 CSRF、开放重定向导致的 XSS、ScrollRestoration 的 SSR XSS;同版新增 allowedActionOrigins 配置与外部 origin 提交的拒绝逻辑
v7.10.1 升级 valibot 修复供应链依赖漏洞

四、v8.0.0:新节奏下的首个大版本(2026-06-17)

CHANGELOG 原文明确:v8 是 React Router 采用新开放治理模型与每年一个大版本节奏后的第一个大版本。选择 6 月是为了对齐 Node 20 的 EOL 时间窗;Node 22 预计 2027 年 5 月 EOL,v9 预计在同时间窗发布。策略上,通过提前以 Future Flags 引入破坏性变更,“如果你在 v7 中已经采用了所有 active future flags,那么从 API 面看,你升级到 v8 已经准备就绪”。

基线支持(Baseline Support)

  • Node 22.22.0+:自 v8 起官方支持所有 Active LTS 版本,以及 Maintenance LTS 的最新 minor 分支;最低 Maintenance LTS 版本的提升将放在 minor 版本中完成;
  • React 19.2.7+
  • Vite 7+
  • 库整体现代化:以 ESM-only 模块发布,tsconfig target/lib 统一提升到 ES2022

已收编的 Future Flag 行为

以下 v8 旗标被移除,行为成为默认:

  • future.v8_trailingSlashAwareDataRequests:尾斜杠感知的数据请求 URL 成为默认;
  • future.v8_passThroughRequests:原始入站 request 永远透传给 loader/action,归一化 URL 请用 url 参数;
  • future.v8_middleware:middleware 永远启用,context 永远是 RouterContextProvider 实例;自定义服务器的 getLoadContext 必须返回 RouterContextProvider(不再接受普通对象);MiddlewareEnabled 类型与 Future 模块增强模式删除;
  • future.v8_viteEnvironmentApi:Vite Environment API 永远启用,预渲染随之切换到基于 Vite preview 服务器(Environment API)的新流程,替换旧的预渲染实现——v7 的 future.unstable_previewServerPrerendering 旗标同时删除;
  • future.v8_splitRouteModules:提升为顶层 splitRouteModules 配置且默认 truefalse 保持单块,"enforce" 强制可拆分)。

移除 react-router-dom

v7 时代 DOM API 已收进 react-router/domreact-router-dom 只是为 v6→v7 迁移保留的 re-export 壳。v8 彻底删除它:RouterProvider/HydratedRouter 需从 react-router/dom 导入,其余一律从 react-router 导入。

移除废弃的 meta data 字段

v7 中路由模块 meta 函数接收的 data 字段已弃用,v8 删除。请在 MetaArgsMetaArgs.matches 每一项上使用 loaderData 替代 data

Cloudflare Vite Plugin 移除

@react-router/dev/vite/cloudflare 开发代理导出在 v8 删除,Cloudflare 项目应改用 @cloudflare/vite-plugin@react-router/dev 因此不再把 wrangler@3 列为 peer 依赖。

其他移除项

  • @react-router/architectcreateRequestContextDomainName 选项删除(useRequestContextDomainName 成为默认行为,v7.18.0 曾以该选项形式先行提供);
  • react-router 移除遗留的 AppLoadContext 类型导出(v8.0.1 补丁修复此项);
  • 内部构建从 tsup 迁移到 tsdown,TypeScript 工具链升级;create-react-router 内部改用原生 fetch(移除对 HTTPS_PROXY 的支持)与 Node 内置工具(移除 arg/strip-ansi/execa 依赖)。

五、v8.1.0 – v8.3.0:稳定期的小步快跑

v8.1.0(2026-06-29)

  • create-react-router 安装 Agent Skills:脚手架可以为新项目安装官方 React Router Agent Skill;交互式 shell 会提示是否包含,--yes 或非交互场景默认包含,可用 --no-agent-skills 跳过;
  • Observability Metadata:即上文第四部分展示的 result.meta/statusCode 能力;
  • 补丁包括:预渲染插件的 buildEnd 时序回归修复、Bun 下 react-router typegen 崩溃修复、react-router-serve 改用 Node 内置网络 API 探测可用端口(移除 get-port 依赖)等。

v8.2.0(2026-07-08):Web Streams 默认服务器入口

非 Node 运行时的 Framework Mode 应用不再需要手写的 renderToReadableStreamentry.server.tsx:带有 @react-router/{node,express,serve} 依赖的应用继续默认 renderToPipeableStream,其余应用默认 renderToReadableStream。Node 应用可通过新旗标主动选择 Web Streams 默认入口(React Router 内部本就使用 Web Streams,可避免额外的 Web/Node 流转换):

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

export default {
  future: {
    unstable_enableNodeReadableStream: true,
  },
} satisfies Config;

注意:如果你已有自定义 entry.server.tsx,该旗标不生效——它只影响“不存在自定义入口时使用的默认入口”。

同版补丁值得注意的路由匹配修复:含可选静态段的路由(如 /school?/user/:id)动态参数提取索引错位;带静态扩展名后缀的动态参数(如 /sitemap.xml 竞争场景)路由排序错误;href() 参数值序列化与 generatePath() 对齐(splat 参数保留路径分隔符、逐段编码)。

v8.3.0(2026-07-22):RSC 入口更新与补丁

该版本面向使用自定义入口的 unstable RSC 应用;使用默认 RSC Framework 入口的应用无需改动。三类定制入口需要跟进:

  1. 客户端版本号:自定义 entry.rsc.tsx 应导入生成的客户端版本并传给 unstable_matchRSCServerRequest(用于懒路由发现时检测过期的 RSC 客户端并刷新文档):
import clientVersion from "virtual:react-router/unstable_rsc/client-version";

return unstable_matchRSCServerRequest({
  // ...
  clientVersion,
});
  1. Subresource Integrity:自定义 app/entry.ssr.tsx 导入新的虚拟模块并把哈希传给 React 的 importMap 渲染选项:
+import subResourceIntegrity from "virtual:react-router/unstable_rsc/subresource-integrity";

return renderToReadableStream(<RSCStaticRouter getPayload={getPayload} />, {
  ...options,
  bootstrapScriptContent,
  formState,
+ importMap: subResourceIntegrity
+   ? { integrity: subResourceIntegrity }
+   : undefined,
  signal: request.signal,
});
  1. CSP nonce:为 unstable_routeRSCServerRequestunstable_RSCStaticRouter 增加 nonce 选项,并转发给 HTML 渲染器,作用于注入的 RSC payload 脚本与感知 nonce 的框架组件。采纳 nonce-based CSP 时,先运行 react-router reveal entry.ssr 查看默认入口,再按如下方式改造(每次请求生成新 nonce 并同时用于响应头):
const nonce = crypto.randomUUID();
const response = await routeRSCServerRequest({
  request,
  serverResponse,
  createFromReadableStream,
  nonce,
  async renderHTML(getPayload, options) {
    const payload = getPayload();
    return renderHTMLToReadableStream(
      <RSCStaticRouter getPayload={getPayload} nonce={options.nonce} />,
      {
        ...options,
        bootstrapScriptContent,
        formState: await payload.formState,
        signal: request.signal,
      },
    );
  },
});
response.headers.set(
  "Content-Security-Policy",
  `script-src 'self' 'nonce-${nonce}'`,
);

其余稳定补丁:href/generatePath 的路径参数编码改为遵循 RFC 3986 path-segment 规则($ & + , ; = : @ 不再百分号编码,如 semver 构建号 1.0.0+1 原样插值;/ ? # %、空白、非 ASCII 依旧转义);createMemorySessionStorage 改用 crypto.randomUUID()(注意它仅面向本地开发与测试,重启即失);NavLinkto 带尾斜杠时不再漏掉 pending 状态;@react-router/architect/cloudflare/dev/express/fs-routes/node/remix-routes-option-adapter 统一放宽以支持 typescript@7

六、从 CHANGELOG 提炼的升级实践建议

结合全文,可以把这份 CHANGELOG 转化为一套可操作的 v7→v8 升级清单:

  1. 先对齐 Future Flags:v8 的破坏性变更全部来自 future.v8_* 旗标。在 v7 上依次启用 v8_trailingSlashAwareDataRequestsv8_passThroughRequestsv8_middlewarev8_viteEnvironmentApi,并把 splitRouteModules 提到顶层配置,全部验证通过后再升 v8——这正是 API 开发策略 设计的升级路径,旗标语义细节见 docs/upgrading/future.md
  2. 把 import 切到 v7 新入口:尽早把 react-router-dom 的导入改为 react-routerreact-router/domRouterProvider/HydratedRouter 用后者),v8 中该包不复存在。
  3. 清理弃用面meta 函数的 data 参数换成 loaderData;自定义服务器的 getLoadContext 返回 RouterContextProvider;移除 MiddlewareEnabled 相关的 declare module "react-router" { interface Future { ... } } 增强。
  4. 升级环境基线:Node 22.22.0+、React 19.2.7+、Vite 7+,并确认构建链可接受 ESM-only 与 ES2022 目标。
  5. 盯住“破坏性 bug 修复”:v7.18.0 的 CSRF host 校验(allowedActionOrigins)、v7.13.2 的 pass-through 请求语义变化、v7.5.1 的 staticHandler.query() context.loaderData 空值行为等,都是 CHANGELOG 明确标注的 "breaking bug fix",升级前应做针对性回归。
  6. 不要在生产使用 unstable API:每条 Unstable Changes 小节都有明确警示;从源码结构与发布节奏看,unstable API 可随 patch 版本随时改名甚至消失(如 unstable_maskmaskunstable_useTransitionsuseTransitions),生产代码应只依赖稳定小节列出的 API。
  7. 安全基线:如果你仍停留在 v7.12.0 之前,请优先处理 CSRF/开放重定向/SSR XSS 三条安全修复,再谈功能升级。

参考资料(本仓库内)

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