React Router v7 到 v8 的发布演进全解析:基于官方 CHANGELOG 的升级与特性速览
本文以仓库根目录的 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 页面,原因有二:
- GitHub 的 UI 分页使得跨越大范围版本检索发布说明变得困难;
- 分页列表视图会截断较长的发布说明,必须点击进入详情才能看到全文。
当前仓库中 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-compat与react-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
json、defer、unstable_composeUploadHandlers、unstable_createMemoryUploadHandler、unstable_parseMultipartFormData 在 v7 中全部移除(defer 的实现被单 fetch + turbo-stream 的原生 Promise 流取代,json 可用 Response.json 替代)。
最低版本要求
node@20,且不再提供installGlobals来为fetch做 polyfill;react@18、react-dom@18。
收编的 Future Flag 行为
v6 的 future.v7_relativeSplatPath、future.v7_startTransition、future.v7_fetcherPersist、future.v7_normalizeFormMethod、future.v7_partialHydration、future.v7_skipActionStatusRevalidation,以及 Remix v2 的 future.v3_fetcherPersist、future.v3_relativeSplatPath、future.v3_throwAbortReason、future.v3_singleFetch、future.v3_lazyRouteDiscovery、future.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 而非 undefined:useNavigate()、useSubmit()、useFetcher().load、useFetcher().submit、useRevalidator().revalidate()。
routes.ts 与类型安全
Framework 模式下路由定义于 app/routes.ts,通过 RouteConfig 类型导出,提供 route、index、layout 辅助函数:
// 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 Modeindex.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: |-|
开启拆分后,路由模块在生产构建期被拆成多个更小的(虚拟)模块,clientLoader 与 Component 可并行下载:
Get clientLoader: |--|
Get Component: |=======|
Run clientLoader: |-----|
Render: |-|
对应的产物是两个虚拟模块:routes/example.tsx?route-chunk=clientLoader(仅含 clientLoader)与 routes/example.tsx?route-chunk=main(含组件及其依赖)。
关键限制:只有当被拆分的导出不共享同文件内的代码时才能拆分。例如 clientLoader 与 Component 都调用同文件的 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/action 的 context 参数从 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;
}
客户端侧同样获得 context:clientLoader/clientAction 在客户端也会收到 unstable_RouterContextProvider 实例,每次导航(或 fetcher 调用)创建全新实例;可在 createBrowserRouter(routes, { unstable_getContext }) 或 <HydratedRouter unstable_getContext> 处提供初始值。
稳定化轨迹:v7.8.0 大幅打磨中间件 API(next 不再抛错、错误冒泡到正确 ErrorBoundary、getLoadContext 签名改为返回 RouterContextProvider 实例而非 Map、staticHandler 的 unstable_respond 重命名为 unstable_generateMiddlewareResponse 且改为回调内执行 query/queryRoute);v7.9.0 正式移除 unstable_ 前缀,RouterContextProvider、createContext、createBrowserRouter 的 getContext、<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 错误
}
路由不在当前页面时返回 undefined(root 是例外,永不返回 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_RSCHydratedRouter、unstable_RSCStaticRouter、unstable_createCallServer、unstable_getRSCStream、unstable_matchRSCServerRequest、unstable_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、loader、action、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(配套类型 ServerInstrumentation、ClientInstrumentation 等去掉 unstable_ 前缀),文档见 docs/how-to/instrumentation.md。
v8.1.0 进一步补充观测元数据:对尚未完成路由匹配的外层(handler、navigate),结果中新增 result.meta(url/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>、useLinkClickHandler、useNavigate、Location) |
mask |
同版还带来路由匹配性能优化:预计算并缓存扁平化/排序后的路由分支,减少关键路径上的 matchRoutes 调用,官方基准显示服务端请求处理提升约 10–15%(客户端侧另一项优化报告约 15–30%)。
其余 v7.x 里程碑速览:
- v7.4.0:为
virtual:react-router/server-build虚拟模块生成类型; - v7.5.1:
dataStrategy新增unstable_runClientMiddleware与unstable_shouldCallHandler/unstable_shouldRevalidateArgs; - v7.9.4:
unstable_getRequest(RSC)等; - v7.10.0:稳定
fetcher.reset()、DataStrategyMatch.shouldCallHandler()/shouldRevalidateArgs、future.v8_splitRouteModules、future.v8_viteEnvironmentApi;新增unstable_useTransitions(控制React.startTransition/React.useOptimistic的使用,React 19 增强行为需显式置true);routes.ts现在在routes.ts求值前先加载环境变量(可基于VITE_前缀环境变量动态拼路由); - v7.11.0:
vite preview支持(预览生产构建)、客户端onError稳定(<RouterProvider onError>/<HydratedRouter onError>); - v7.12.0:
future.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.2:
future.unstable_passThroughRequests+unstable_url参数(request原样透传,.data后缀与index/_routes查询参数可见,unstable_url提供归一化 URL),v7.16.0 稳定为future.v8_trailingSlashAwareDataRequests,v8.0.0 后成为默认且url正式落地; - v7.15.1:
unstable_useRouterState()(active/pending双结构整合useLocation/useSearchParams/useParams/useMatches/useNavigationType/useNavigation等十余个 hook,是“Less is More”设计目标的产物,部分旧 hook 可能在未来版本被弃用); - v7.18.0(2026-06-16):CSRF 检查逻辑修复——改为直接校验
requestURL 中的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配置且默认true(false保持单块,"enforce"强制可拆分)。
移除 react-router-dom
v7 时代 DOM API 已收进 react-router/dom,react-router-dom 只是为 v6→v7 迁移保留的 re-export 壳。v8 彻底删除它:RouterProvider/HydratedRouter 需从 react-router/dom 导入,其余一律从 react-router 导入。
移除废弃的 meta data 字段
v7 中路由模块 meta 函数接收的 data 字段已弃用,v8 删除。请在 MetaArgs 及 MetaArgs.matches 每一项上使用 loaderData 替代 data。
Cloudflare Vite Plugin 移除
@react-router/dev/vite/cloudflare 开发代理导出在 v8 删除,Cloudflare 项目应改用 @cloudflare/vite-plugin;@react-router/dev 因此不再把 wrangler@3 列为 peer 依赖。
其他移除项
@react-router/architect的createRequestContextDomainName选项删除(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 应用不再需要手写的 renderToReadableStream 版 entry.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 入口的应用无需改动。三类定制入口需要跟进:
- 客户端版本号:自定义
entry.rsc.tsx应导入生成的客户端版本并传给unstable_matchRSCServerRequest(用于懒路由发现时检测过期的 RSC 客户端并刷新文档):
import clientVersion from "virtual:react-router/unstable_rsc/client-version";
return unstable_matchRSCServerRequest({
// ...
clientVersion,
});
- 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,
});
- CSP nonce:为
unstable_routeRSCServerRequest与unstable_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()(注意它仅面向本地开发与测试,重启即失);NavLink 在 to 带尾斜杠时不再漏掉 pending 状态;@react-router/architect/cloudflare/dev/express/fs-routes/node/remix-routes-option-adapter 统一放宽以支持 typescript@7。
六、从 CHANGELOG 提炼的升级实践建议
结合全文,可以把这份 CHANGELOG 转化为一套可操作的 v7→v8 升级清单:
- 先对齐 Future Flags:v8 的破坏性变更全部来自
future.v8_*旗标。在 v7 上依次启用v8_trailingSlashAwareDataRequests、v8_passThroughRequests、v8_middleware、v8_viteEnvironmentApi,并把splitRouteModules提到顶层配置,全部验证通过后再升 v8——这正是 API 开发策略 设计的升级路径,旗标语义细节见 docs/upgrading/future.md。 - 把 import 切到 v7 新入口:尽早把
react-router-dom的导入改为react-router与react-router/dom(RouterProvider/HydratedRouter用后者),v8 中该包不复存在。 - 清理弃用面:
meta函数的data参数换成loaderData;自定义服务器的getLoadContext返回RouterContextProvider;移除MiddlewareEnabled相关的declare module "react-router" { interface Future { ... } }增强。 - 升级环境基线:Node 22.22.0+、React 19.2.7+、Vite 7+,并确认构建链可接受 ESM-only 与 ES2022 目标。
- 盯住“破坏性 bug 修复”:v7.18.0 的 CSRF host 校验(
allowedActionOrigins)、v7.13.2 的 pass-through 请求语义变化、v7.5.1 的staticHandler.query()context.loaderData空值行为等,都是 CHANGELOG 明确标注的 "breaking bug fix",升级前应做针对性回归。 - 不要在生产使用 unstable API:每条 Unstable Changes 小节都有明确警示;从源码结构与发布节奏看,unstable API 可随 patch 版本随时改名甚至消失(如
unstable_mask→mask、unstable_useTransitions→useTransitions),生产代码应只依赖稳定小节列出的 API。 - 安全基线:如果你仍停留在 v7.12.0 之前,请优先处理 CSRF/开放重定向/SSR XSS 三条安全修复,再谈功能升级。
参考资料(本仓库内)
- 发布说明主体:CHANGELOG.md(v7.0.0 → v8.3.0 全量条目)
- API 开发策略(Future Flags / Unstable Flags 语义):docs/community/api-development-strategy.md
- Future Flags 迁移指南:docs/upgrading/future.md
- 中间件设计与实现说明:decisions/0014-context-middleware.md、docs/how-to/middleware.md
- 观测/插桩文档:docs/how-to/instrumentation.md
- RSC 文档:docs/how-to/react-server-components.md
- 发布工作流脚本:scripts/changes/add.ts、scripts/changes/version.ts、scripts/changes/publish.ts
- 当前版本快照:packages/react-router/package.json(
version: 8.3.0)
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00