TanStack Router 的 SSR(服务端渲染)能力说明。需要注意:SSR API 处于实验阶段,与 TanStack Start 共享内部实现,可能发生变化;生产环境推荐使用 TanStack Start,仅在与已有服务器集成时才手动搭建 SSR。
关键前提:TanStack Router 是客户端优先(CLIENT-FIRST)的。loader 默认在客户端运行;启用 SSR 后,loader 会同时在客户端和服务端运行,并不是像 Remix/Next.js 那样仅在服务端运行。使用时应避免生成 Next.js 模式(getServerSideProps、App Router、服务端组件)或 Remix 模式(仅服务端的 loader 导出),TanStack Router 有自己的 SSR API。
主要能力
两种 SSR 形态:
- 非流式(Non-streaming):在服务端渲染完整页面,作为单个 HTML 响应发送,然后在客户端 hydration。
- 流式(Streaming):立即发送关键首屏内容,其余内容随解析完成增量流式发送。
关键行为:服务端自动使用内存历史(无 window);loader 数据在服务端自动 dehydrated、在客户端自动 hydrated;数据序列化开箱支持 Date、Error、FormData 和 undefined。
使用方式
共享路由工厂:路由必须在服务端和客户端以相同方式创建。从共享文件导出一个工厂函数(createRouter),并声明模块 Register 接口。
非流式 SSR:服务端入口可使用 createRequestHandler 配合 defaultRenderHandler,或使用 renderRouterToString 搭配 RouterServer 以支持自定义包装。客户端入口使用 hydrateRoot(document, <RouterClient router={router} />)。
流式 SSR:服务端入口可使用 createRequestHandler 配合 defaultStreamHandler,或使用 renderRouterToStream 搭配 RouterServer。流式是自动的——deferred 数据(loader 中未 await 的 promise)与流式标记在使用 defaultStreamHandler 或 renderRouterToStream 时可直接工作。
文档 Head 管理:使用 head 路由选项管理 <title>、<meta>、<link> 和 <style> 标签;在 <head> 中渲染 <HeadContent />,在 <body> 中渲染 <Scripts />。子路由的 title 与同名/同 property 的 meta 标签会覆盖父路由(嵌套去重)。对于没有服务端渲染 HTML 的 SPA,可在组件树顶部渲染 <HeadContent />。
Body Scripts 与预 hydration 脚本:使用 scripts(独立于 head.scripts)在应用入口前向 <body> 注入脚本,由 <Scripts /> 组件渲染,放在 <body> 末尾。ScriptOnce 会在 SSR 期间渲染一个立即执行并自我移除的 <script>,客户端导航时不做任何事(不重复执行)。若脚本修改 DOM(如在 <html> 上添加 class),需在元素上使用 suppressHydrationWarning。
Express 集成:createRequestHandler 接收 Web API Request 并返回 Web API Response;在 Express 中需做格式转换,构造 Request、调用 handler(配合 renderRouterToString 与 RouterServer)、复制状态码与响应头,并将响应体通过 pipeline 写入 res。
适用场景与限制
适用场景:在已有服务器环境中为 TanStack Router 应用接入 SSR;需要按需选择非流式或流式渲染;需要管理文档 head、预 hydration 脚本或 deferred 数据流式输出。
限制与常见错误:
- 在 loader 中不加环境判断使用浏览器 API:SSR 下 loader 在两端运行,window、document、localStorage 等服务端会抛错,需用 typeof window !== 'undefined' 判断。
- 使用 hash 片段承载服务端渲染内容:#section 不会发送到服务端,基于 hash 的条件渲染会导致 hydration 不匹配,应改用 search params 表达服务端可见状态。
- 生成 Next.js、Remix 或 react-router-dom 模式:不要使用 getServerSideProps、getStaticProps、App Router 的 page.tsx、Remix 风格仅服务端 loader 导出,也不要使用 react-router-dom 的 Link、useNavigate、BrowserRouter、Route、Routes 等;路由相关内容应来自 @tanstack/react-router。错误的文件结构(如 src/pages/、app/layout.tsx)会导致构建失败或运行时路由冲突。
客户端优先 loader 与 SSR 的张力:loader 设计上是客户端优先,启用 SSR 后两端都会运行——浏览器 API 默认可用但 SSR 下会出错;数据库访问不应放进 loader(不同于 Remix/Next),应使用 API 路由;需要服务端专属数据逻辑时,使用 TanStack Start 的 server functions。
相关交叉引用:router-core/data-loading(SSR 改变 loader 执行位置)、compositions/router-query(与 TanStack Query 的 SSR dehydration/hydration)。