React Router `<StaticRouter>` 完全指南:在无状态环境中进行静态与服务端渲染
在 React Router 的 declarative(声明式)模式下,<StaticRouter> 是专为服务端渲染(SSR)、静态 HTML 生成与同构测试场景设计的路由组件:它只负责“把给定的 Location 渲染成一次性的 UI 快照”,不会对任何导航行为做出响应。本文将围绕 StaticRouter 的 API 签名、三个 Props(basename、children、location)展开,并结合 packages/react-router/lib/dom/server.tsx 的底层实现与 __tests__/dom/ 下的测试用例,讲清它的归一化规则、禁止导航的原理及完整实战用法,让你能直接拿它在 ReactDOM 的静态渲染 API 中构建可运行的 HTML 输出。
StaticRouter 是什么:为无状态环境而生的 Router
<StaticRouter> 是 <Router> 的一个特殊封装。官方摘要将其定义为:
A
<Router>that may not navigate to any other Location. This is useful on the server where there is no stateful UI.
也就是说,它本质上仍然是一个提供 location 上下文、渲染路由树的 <Router>,但被设计成不允许跳转到任何其他 Location。原因在于:服务端渲染过程是一次性的,请求到来后你只有“当前这一次 URL”,服务端没有浏览器里那种可以前进后退的历史状态,也不应该触发导航副作用(例如把 navigate() 的结果写进 history)。
从 React Router 的模式划分看(docs/start/modes.md),<StaticRouter> 属于 declarative 模式的路由组件,与 BrowserRouter、MemoryRouter 等并列;它的声明式定位使其可以不经 createBrowserRouter 这类数据路由工厂,直接配合声明式的 <Routes> 与 <Route>(即 JSX 路由配置)使用。
API 签名与 Props 速览
StaticRouter 是声明式模式下可直接从 react-router 导入的公共导出(参见 packages/react-router/index.ts#L215-L216):
export {
// ...
StaticRouter,
// ...
};
组件完整的类型签名如下(docs/api/declarative-routers/StaticRouter.md):
function StaticRouter({
basename,
children,
location: locationProp = "/",
}: StaticRouterProps)
对应的 Props 类型定义在 packages/react-router/lib/dom/server.tsx#L40-L53:
export interface StaticRouterProps {
/**
* The base URL for the static router (default: `/`)
*/
basename?: string;
/**
* The child elements to render inside the static router
*/
children?: React.ReactNode;
/**
* The Location to render the static router at (default: `/`)
*/
location: Partial<Location> | string;
}
| Prop | 类型 | 默认值 | 作用 |
|---|---|---|---|
basename |
string(可选) |
/ |
静态路由所在的基础 URL,会被前置拼接到所有待匹配的 location 上 |
children |
React.ReactNode(可选) |
无 | 在静态路由内部渲染的子元素,通常是 <Routes>/<Route> 组成的路由树 |
location |
string 或 Partial<Location>(必填但有默认值) |
"/" |
静态路由要渲染到的 Location,即“当前地址”;类型层面必传,省略时默认解析为根路径 / |
location Prop 深度解析:字符串与对象的归一化
location 是 <StaticRouter> 最关键、也最有信息量的 Prop。它可以接受两种形态,源码会先把它们统一归一化成一个完整的 Location 对象再交给内部 <Router>:
// packages/react-router/lib/dom/server.tsx#L68-L99
export function StaticRouter({
basename,
children,
location: locationProp = "/",
}: StaticRouterProps) {
if (typeof locationProp === "string") {
locationProp = parsePath(locationProp);
}
let action = NavigationType.Pop;
let location: Location = {
pathname: locationProp.pathname || "/",
search: locationProp.search || "",
hash: locationProp.hash || "",
state: locationProp.state != null ? locationProp.state : null,
key: locationProp.key || "default",
mask: undefined,
};
let staticNavigator = getStatelessNavigator();
return (
<Router
basename={basename}
children={children}
location={location}
navigationType={action}
navigator={staticNavigator}
static={true}
useTransitions={false}
/>
);
}
1. 字符串形态(常见做法)
当 location 是字符串时,实现会调用 parsePath(来自 packages/react-router/lib/router/history.ts)把它拆解成 pathname、search、hash 三部分。例如:
<StaticRouter location="/the/path?the=query#the-hash">
测试 packages/react-router/tests/dom/static-location-test.tsx#L5-L30 通过 useLocation() 精确验证了这条路径:字符串 "/the/path?the=query#the-hash" 会被解析为
{
pathname: "/the/path",
search: "?the=query",
hash: "#the-hash",
state: null,
key: expect.any(String),
}
2. 对象形态(自动补全缺失字段)
当 location 是 Partial<Location> 对象时,源码会对其缺省字段逐一补齐默认值:
pathname缺省 →"/"search缺省 →""hash缺省 →""state为null/undefined→nullkey缺省 →"default"(这是静态场景下的哨兵 key,因为服务端渲染没有真实的历史条目)mask→ 恒为undefined
例如 static-location-test.tsx#L32-L57 中传入 { pathname: "/the/path", search: "?the=query" },最终拿到的 location 是 pathname: "/the/path"、search: "?the=query"、hash: ""、state: null、key 为字符串。同文件 #L59-L83 还验证了 state: 0 这类非 null 显式 state 会被原样保留(因为使用了 != null 而非 !== undefined 判断,0 不会被误替换)。
为什么它“不能导航”:静态 Navigator 与 static 标志
<StaticRouter> 与浏览器路由最本质的区别在于两处:其一是向内层 <Router> 传入的 static={true},让路由树不再对外部 location 变化保持响应;其二是传入了一个 getStatelessNavigator() 生成的无状态 navigator。
见 packages/react-router/lib/dom/server.tsx#L263-L302:
function getStatelessNavigator() {
return {
createHref,
encodeLocation,
push(to: To) {
throw new Error(
`You cannot use navigator.push() on the server because it is a stateless ` +
`environment. ...`,
);
},
replace(to: To) { /* 同样 throw */ },
go(delta: number) { /* 同样 throw */ },
back() { /* 同样 throw */ },
forward() { /* 同样 throw */ },
};
}
这个对象只实现了两件“只读”能力:createHref(把 To 转成字符串 href,见 #L454-L456)和 encodeLocation(把 href 编码成 Path,见 #L458-L472),其余 push/replace/go/back/forward 五个导航方法全部直接抛出异常,错误信息中会明示“stateless environment”,并附带触发导航的目标地址,帮助开发者定位到服务端代码中误用 navigate(...) 的位置。
同时组件还把 navigationType 固定为 NavigationType.Pop、useTransitions={false}:既然不存在真实跳转,历史动作恒为 Pop(表示“到达当前地址”),也不需要 startTransition 去包装导航状态更新。
值得注意:
<StaticRouter>是 declarative 声明式路由的静态渲染组件,与 data 模式下的StaticRouterProvider(配合createStaticHandler/createStaticRouter做 loader/action 数据请求后再渲染)职责不同——前者只渲染手写的<Routes>树,不执行任何数据加载。
实战:用 renderToStaticMarkup 输出 HTML 快照
在服务端,<StaticRouter> 最常见的宿主是 ReactDOM 的 renderToStaticMarkup(产出无交互注解的纯 HTML)或 renderToString。典型用法如下:
import * as React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { StaticRouter, Routes, Route } from "react-router";
function Home() {
return <h1>Hello from the server</h1>;
}
function renderApp(url: string) {
return renderToStaticMarkup(
<StaticRouter location={url}>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</StaticRouter>,
);
}
// 按每个入站请求的 URL 渲染一份静态 HTML
console.log(renderApp("/"));
关键的工程含义包括:
- URL 来自外部、逐请求提供:把请求的 path 当作
location传入,渲染结果对应当前地址的唯一快照;同一组件树可反复用不同location调用以输出多个页面(预渲染/静态站点生成场景,可直接改写为对一组路由路径循环渲染)。 - 必须避免导航意图:测试 packages/react-router/tests/dom/static-navigate-test.tsx#L5-L32 说明,若在
<StaticRouter>内于首次渲染时使用<Navigate>(它会尝试在初始渲染后立即重定向),会触发控制台告警"<Navigate> must not be used on the initial render"。这类“跟随重定向再渲染”的职责应交给 data 模式的静态处理器在渲染前完成,而不是由声明式组件在服务端自行跳转。 - 一切路由 hook 照常可用:
useLocation、useParams、useMatch等在StaticRouter内读取的是被归一化后的静态 location,测试即通过useLocation()断言归一化结果,说明静态渲染下上下文完备。 basename语义:当应用部署在子路径(如/app)下时,传basename="/app",即可让<Routes>中的相对路径都基于该前缀匹配,与<Router>中“basename 会被前置拼接到所有 locations”的行为一致。
与声明式路由家族的关系及选型对照
<StaticRouter> 站在声明式 <Router> 之上,形成如下图示的能力约束(参考 docs/api/declarative-routers/index.md 的分类):
| 组件 | 能否导航 | 典型场景 |
|---|---|---|
<BrowserRouter> |
是(history API) | 浏览器端 SPA 的默认选择 |
<HashRouter> |
是(hash 同步) | 无法配置服务器的静态托管 |
<MemoryRouter> |
是(内存内) | 测试与非浏览器环境 |
<StaticRouter> |
否(所有导航方法抛错) | SSR、静态 HTML 生成、同构测试 |
<ServerRouter> |
否 | Framework 模式下的完整服务端渲染入口 |
从源码结构看(packages/react-router/lib/dom/server.tsx 同文件),React Router 把“静态渲染”能力按模式拆成了三层:声明式模式的 <StaticRouter>(本文主角)、data 模式的 StaticRouterProvider + createStaticRouter(docs/api/data-routers/createStaticRouter.md)、以及 RSC 场景的 unstable_RSCStaticRouter(docs/api/rsc/RSCStaticRouter.md)。它们都复用了本文件中的 getStatelessNavigator、createHref、encodeLocation 等无状态基础设施。如果你需要 loader/action 数据加载后的 SSR,则应升级到 data 模式方案;若只是“给固定 URL 出一份 HTML”,<StaticRouter> 是最轻量直接的选择。
小结
<StaticRouter> 用极小的 API 面(三个 Props)承担了声明式 React Router 在服务端/无状态环境的全部渲染职责:字符串或对象形态的 location 会被 parsePath + 字段补齐归一化为完整 Location;static={true} 使其对外部变化免疫;getStatelessNavigator() 让任何 push/replace/go/back/forward 调用都以明确报错终止,从机制上杜绝了服务端产生导航副作用。配合 react-dom/server 的渲染 API,即可写出逐请求输出 HTML 的同构入口,或用于需要精确控制当前地址的渲染测试。
想继续深入可以查看:
- 完整组件实现:packages/react-router/lib/dom/server.tsx
- location 归一化验证:packages/react-router/tests/dom/static-location-test.tsx
<Navigate>在静态路由中的告警:packages/react-router/tests/dom/static-navigate-test.tsx- 声明式路由入口:docs/api/declarative-routers/index.md
- 声明式模式路由配置:docs/start/declarative/routing.md
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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00