ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

react-router StaticRouterProvider 深度解析:静态数据路由器的 SSR 渲染与 Hydration 数据原理

2026/9/7 6:41:05 拓冰建站 浏览量
react-router StaticRouterProvider 深度解析:静态数据路由器的 SSR 渲染与 Hydration 数据原理 react-router StaticRouterProvider 深度解析静态数据路由器的 SSR 渲染与 Hydration 数据原理【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router在 React 应用中做服务端渲染SSR时StaticRouterProvider是 react-router 数据路由Data Router模式下打通“服务器 → 浏览器”这条链路的核心组件。它接收由createStaticHandler发起数据请求后得到的StaticHandlerContext与静态DataRouter在服务端一次性渲染出完整的 HTML并把 loader/action 数据以脚本形式嵌入页面供客户端水合时直接复用。读完本文你将掌握StaticRouterProvider的完整用法、四个 props 的精确语义、Hydration 脚本的双重序列化与安全转义机制以及它在无状态导航环境下的实现细节。定位一个“不能导航”的 DataRouter官方文档StaticRouterProvider.md对它的定义非常简洁ADataRouterthat may not navigate to any otherLocation. This is useful on the server where there is no stateful UI.即它是一个不允许跳转到任何其他Location的数据路由器适用于没有有状态 UI 的服务器环境。它的完整签名是function StaticRouterProvider({ context, router, hydrate true, nonce, }: StaticRouterProviderProps)文档给出的标准用法来自 JSDoc见 server.tsx 的example如下export async function handleRequest(request: Request) { let { query, dataRoutes } createStaticHandler(routes); let context await query(request); if (context instanceof Response) { return context; } let router createStaticRouter(dataRoutes, context); return new Response( ReactDOMServer.renderToString(StaticRouterProvider ... /), { headers: { Content-Type: text/html } } ); }这个示例浓缩了完整的 SSR 请求处理流水线值得逐行理解createStaticHandler(routes)创建静态处理器返回{ query, queryRoute, dataRoutes }。其中query()面向文档请求document requestsqueryRoute()面向定向路由请求dataRoutes是内部转换后的数据路由对象签名与参数详见 createStaticHandler.md实现位于 router.ts。await query(request)执行 loader/action 数据请求。返回值有两种可能StaticHandlerContext正常渲染所需的数据上下文Response实例通常是重定向或 loader 抛出的Response例如throw redirect(/login)。此时应直接把该Response作为当前请求的返回值不再渲染。createStaticRouter(dataRoutes, context)用query的返回上下文构造静态路由器签名见 createStaticRouter.md。renderToString(StaticRouterProvider ... /)把静态路由器渲染为 HTML 字符串包装为text/html响应返回。提示如果路由使用了lazy路由模块务必使用createStaticHandler解构出的dataRoutes传给createStaticRouter如上例而不是原始routes。因为 handler 内部已完成懒加载模块的解析与路由增强dataRoutes才是与context匹配的对象。这一点在测试>get state() { return { historyAction: NavigationType.Pop, location: context.location, matches, loaderData: context.loaderData, actionData: context.actionData, errors: context.errors, initialized: true, navigation: IDLE_NAVIGATION, fetchers: new Map(), blockers: new Map(), // ... }; },所有动态方法navigate、fetch、revalidate、initialize、subscribe、dispose等都会抛出形如You cannot use router.navigate() on the server because it is a stateless environment的错误见 server.tsx。这与StaticRouterProvider“不可导航” 的定位完全一致——服务端只有一次渲染机会任何试图改变 URL 的调用都必须被显式禁止。hydrate文档说明Whether to hydrate the router on the client (defaulttrue)控制是否在 HTML 中输出水合脚本。默认值为true。当设为false时页面不包含任何内联script客户端不会执行水合适用于预渲染纯静态页面或测试场景。测试用例>DataRouterContext.Provider value{dataRouterContext} DataRouterStateContext.Provider value{state} FetchersContext.Provider value{fetchersContext} ViewTransitionContext.Provider value{{ isTransitioning: false }} Router ... static{true} useTransitions{false} DataRoutes manifest{router.manifest} routes{router.routes} future{router.future} state{state} isStatic{true} / /Router /ViewTransitionContext.Provider /FetchersContext.Provider /DataRouterStateContext.Provider /DataRouterContext.Provider几个值得注意的实现决策fetchersContext恒为空Map服务端渲染不产生 fetcher 请求getFetcher()也固定返回IDLE_FETCHERViewTransitionContext固定为{ isTransitioning: false }静态渲染不存在视图过渡状态Router传入static{true}与useTransitions{false}内部组件据此走静态分支如Link渲染为纯a>if (hydrate ! false) { let data { loaderData: context.loaderData, actionData: context.actionData, errors: serializeErrors(context.errors), }; // Use JSON.parse here instead of embedding a raw JS object here to speed // up parsing on the client. Dual-stringify is needed to ensure all quotes // are properly escaped in the resulting string. let json escapeHtml(JSON.stringify(JSON.stringify(data))); hydrateScript window.__staticRouterHydrationData JSON.parse(${json});; }这段实现回答了两个容易被忽略的问题为什么双重JSON.stringify直接内联一个 JS 对象字面量需要逐字段手写转义逻辑而把 JSON 字符串再次 stringify 成字符串字面量可以交给 JSON 规范本身完成全部引号转义。客户端侧再执行一次JSON.parse即可还原对象。源码注释明确提到这样做是为了客户端解析更快引用了 V8 团队关于 JSON 解析性能的分析。测试>scriptwindow.__staticRouterHydrationData JSON.parse({...});/script为什么还要escapeHtmlloader 返回的数据里完全可能包含/script这类破坏 HTML 结构的字符串。escapeHtml将其转义为\u003c形式防止内联脚本提前终止。测试用例>push(to: To) { throw new Error( You cannot use navigator.push() on the server because it is a stateless environment. This error was probably triggered when you did a \navigate(${JSON.stringify(to)})\ somewhere in your app., ); },replace、go、back、forward同理。注意错误信息里直接内嵌了触发导航的参数JSON.stringify(to)这在排查“loader 里意外调用navigate”这类问题时非常有用——服务端渲染期间任何navigate(...)都会带着目标路径直接炸出。encodeLocation还有一个细节对href结尾的空格做%20预编码server.tsx因为把 href 当作完整 URL 解析时尾部空格会被吃掉而这些空格可能是祖先路由 splat 参数的合法部分。测试用例>import * as React from react; import * as ReactDOMServer from react-dom/server; import { createStaticHandler, createStaticRouter, StaticRouterProvider, } from react-router; let staticHandler createStaticHandler(routes); export async function handleRequest(request: Request) { let context await staticHandler.query(request); // 重定向或 loader 抛出的 Response 直接透传 if (context instanceof Response) { return context; } let html ReactDOMServer.renderToString( React.StrictMode StaticRouterProvider router{createStaticRouter(staticHandler.dataRoutes, context)} context{context} nonce{nonce} // 启用 CSP 时传入 hydrate{true} // 默认值可省略 / /React.StrictMode ); return new Response( !DOCTYPE htmlhtmlbodydiv idroot${html}/div/body/html, { headers: { Content-Type: text/html } } ); }要点回顾createStaticHandler可以只创建一次并复用于每个请求上例在模块顶层创建文档示例中每次请求内联创建同样合法query返回Response时必须短路返回这是处理redirect的标准姿势lazy路由场景必须传staticHandler.dataRoutes而非原始routes客户端水合脚本写入window.__staticRouterHydrationData包含loaderData、actionData、errors已按上文规则序列化浏览器端水合时消费该数据避免 loader 重复执行。小结StaticRouterProvider是 react-router 数据路由模式下的 SSR 出口它用StaticHandlerContext 静态DataRouter完成一次不可导航的渲染通过四层 Context 组织输出用双重JSON.stringify HTML 转义安全地输出window.__staticRouterHydrationData水合脚本并用有状态的错误序列化__type/__subType标记、剔除服务端 stack保证错误对象安全地跨边界传输。相关实现集中在 packages/react-router/lib/dom/server.tsx行为边界由 packages/react-router/tests/dom/data-static-router-test.tsx 的十余个用例hydration 脚本格式、转义、nonce、禁用 hydration、缺参报错、lazy 路由、basename、边界追踪等完整覆盖可作为自研 SSR 适配层时的行为参照。【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考