首页
/ React Router `<StaticRouter>` 完全指南:在无状态环境中进行静态与服务端渲染

React Router `<StaticRouter>` 完全指南:在无状态环境中进行静态与服务端渲染

2026-09-07 09:28:40作者:袁立春Spencer

在 React Router 的 declarative(声明式)模式下,<StaticRouter> 是专为服务端渲染(SSR)、静态 HTML 生成与同构测试场景设计的路由组件:它只负责“把给定的 Location 渲染成一次性的 UI 快照”,不会对任何导航行为做出响应。本文将围绕 StaticRouter 的 API 签名、三个 Props(basenamechildrenlocation)展开,并结合 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 模式的路由组件,与 BrowserRouterMemoryRouter 等并列;它的声明式定位使其可以不经 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 stringPartial<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)把它拆解成 pathnamesearchhash 三部分。例如:

<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. 对象形态(自动补全缺失字段)

locationPartial<Location> 对象时,源码会对其缺省字段逐一补齐默认值:

  • pathname 缺省 → "/"
  • search 缺省 → ""
  • hash 缺省 → ""
  • statenull/undefinednull
  • key 缺省 → "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: nullkey 为字符串。同文件 #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.PopuseTransitions={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("/"));

关键的工程含义包括:

  1. URL 来自外部、逐请求提供:把请求的 path 当作 location 传入,渲染结果对应当前地址的唯一快照;同一组件树可反复用不同 location 调用以输出多个页面(预渲染/静态站点生成场景,可直接改写为对一组路由路径循环渲染)。
  2. 必须避免导航意图:测试 packages/react-router/tests/dom/static-navigate-test.tsx#L5-L32 说明,若在 <StaticRouter> 内于首次渲染时使用 <Navigate>(它会尝试在初始渲染后立即重定向),会触发控制台告警 "<Navigate> must not be used on the initial render"。这类“跟随重定向再渲染”的职责应交给 data 模式的静态处理器在渲染前完成,而不是由声明式组件在服务端自行跳转。
  3. 一切路由 hook 照常可用useLocationuseParamsuseMatch 等在 StaticRouter 内读取的是被归一化后的静态 location,测试即通过 useLocation() 断言归一化结果,说明静态渲染下上下文完备。
  4. 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 + createStaticRouterdocs/api/data-routers/createStaticRouter.md)、以及 RSC 场景的 unstable_RSCStaticRouterdocs/api/rsc/RSCStaticRouter.md)。它们都复用了本文件中的 getStatelessNavigatorcreateHrefencodeLocation 等无状态基础设施。如果你需要 loader/action 数据加载后的 SSR,则应升级到 data 模式方案;若只是“给固定 URL 出一份 HTML”,<StaticRouter> 是最轻量直接的选择。

小结

<StaticRouter> 用极小的 API 面(三个 Props)承担了声明式 React Router 在服务端/无状态环境的全部渲染职责:字符串或对象形态的 location 会被 parsePath + 字段补齐归一化为完整 Locationstatic={true} 使其对外部变化免疫;getStatelessNavigator() 让任何 push/replace/go/back/forward 调用都以明确报错终止,从机制上杜绝了服务端产生导航副作用。配合 react-dom/server 的渲染 API,即可写出逐请求输出 HTML 的同构入口,或用于需要精确控制当前地址的渲染测试。

想继续深入可以查看:

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391