框架
版本

SSR

Warning

虽然已经尽力将这些 API 与 Tanstack Start 的变更分离,但内部仍有底层共享实现。因此,这些 API 可能会发生变化,在 Start 达到稳定状态之前应被视为实验性的。

服务器端渲染(SSR)是在服务器上渲染组件并将 HTML 标记发送到客户端的过程。然后客户端将标记水合为完全交互式的组件。

通常有两种不同的 SSR 风格需要考虑:

  • 非流式 SSR
    • 整个页面在服务器上渲染,并在一个单独的 HTML 请求中发送到客户端,包括应用程序在客户端水合所需的序列化数据。
  • 流式 SSR
    • 页面的关键首次绘制在服务器上渲染,并在一个单独的 HTML 请求中发送到客户端,包括应用程序在客户端水合所需的序列化数据
    • 页面的其余部分随后在服务器上渲染时流式传输到客户端。

本指南将解释如何使用 TanStack Router 实现这两种 SSR 风格!

非流式 SSR

非流式服务器端渲染是在服务器上为整个应用程序页面渲染标记并将完整的 HTML 标记(和数据)发送到客户端的经典过程。然后客户端将标记水合为完全交互式的应用程序。

要使用 TanStack Router 实现非流式 SSR,您需要以下实用程序:

  • 来自 @tanstack/react-routerRouterClient
    • 例如 <RouterClient router={router} />
    • 在客户端入口中渲染此组件将渲染您的应用程序,并自动实现 Router 上的 Wrap 组件选项
  • 以及以下之一:
    • 来自 @tanstack/react-routerdefaultRenderHandler
      • 这将在您的服务器入口中渲染您的应用程序,并自动处理应用程序级别的水合/脱水,同时自动实现 RouterServer 组件。 或者:
    • 来自 @tanstack/react-routerrenderRouterToString
      • 这与 defaultRenderHandler 的不同之处在于,它允许您手动指定 Router 上的 Wrap 组件选项以及您可能需要包装的任何其他提供者。
    • 来自 @tanstack/react-routerRouterServer
      • 这实现了 Router 上的 Wrap 组件选项

自动服务器历史记录

在客户端,Router 默认使用 createBrowserHistory 的实例,这是在客户端使用的首选历史记录类型。但是,在服务器上,您需要使用 createMemoryHistory 的实例。这是因为 createBrowserHistory 使用 window 对象,而该对象在服务器上不存在。这在 RouterServer 组件中会自动为您处理。

自动加载器脱水/水合

只要您完成本指南中概述的标准 SSR 步骤,路由获取的已解析加载器数据就会被 TanStack Router 自动脱水和重新水合。

⚠️ 如果您使用延迟数据流,您还需要确保已实现本指南末尾附近的 SSR 流式传输和流转换 模式。

有关如何利用数据加载的更多信息,请参阅 数据加载 指南。

路由器创建

由于您的路由器将同时存在于服务器和客户端,因此重要的是以在这两个环境之间保持一致的方式创建路由器。最简单的方法是在共享文件中公开一个 createRouter 函数,该函数可以被服务器和客户端入口文件导入和调用。

tsx
// src/router.tsx
import { createRouter as createTanstackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function createRouter() {
  return createTanstackRouter({ routeTree })
}

declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}
// src/router.tsx
import { createRouter as createTanstackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function createRouter() {
  return createTanstackRouter({ routeTree })
}

declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof createRouter>
  }
}

在服务器上渲染应用程序

现在您有了一个已为当前 URL 加载所有关键数据的路由器实例,您可以在服务器上渲染您的应用程序:

使用 defaultRenderToString

tsx
// src/entry-server.tsx
import {
  createRequestHandler,
  defaultRenderToString,
} from '@tanstack/react-router/ssr/server'
import { createRouter } from './router'

export async function render({ request }: { request: Request }) {
  const handler = createRequestHandler({ request, createRouter })

  return await handler(defaultRenderToString)
}
// src/entry-server.tsx
import {
  createRequestHandler,
  defaultRenderToString,
} from '@tanstack/react-router/ssr/server'
import { createRouter } from './router'

export async function render({ request }: { request: Request }) {
  const handler = createRequestHandler({ request, createRouter })

  return await handler(defaultRenderToString)
}

使用 renderRouterToString

tsx
// src/entry-server.tsx
import {
  createRequestHandler,
  renderRouterToString,
  RouterServer,
} from '@tanstack/react-router/ssr/server'
import { createRouter } from './router'

export function render({ request }: { request: Request }) {
  const handler = createRequestHandler({ request, createRouter })

  return handler(({ request, responseHeaders, router }) =>
    renderRouterToString({
      request,
      responseHeaders,
      router,
      children: <RouterServer router={router} />,
    }),
  )
}
// src/entry-server.tsx
import {
  createRequestHandler,
  renderRouterToString,
  RouterServer,
} from '@tanstack/react-router/ssr/server'
import { createRouter } from './router'

export function render({ request }: { request: Request }) {
  const handler = createRequestHandler({ request, createRouter })

  return handler(({ request, responseHeaders, router }) =>
    renderRouterToString({
      request,
      responseHeaders,
      router,
      children: <RouterServer router={router} />,
    }),
  )
}

注意:createRequestHandler 方法需要一个 web api 标准的 Request 对象,而 handler 方法将返回一个 web api 标准的 Response promise。

如果您使用像 Express 这样使用自己的 Request 和 Response 对象的服务器框架,您需要从一个转换到另一个。请查看示例以了解这样的实现可能是什么样子。

在客户端渲染应用程序

在客户端,事情要简单得多。

  • 创建您的路由器实例
  • 使用 <RouterClient /> 组件渲染您的应用程序
tsx
// src/entry-client.tsx
import { hydrateRoot } from 'react-dom/client'
import { RouterClient } from '@tanstack/react-router/ssr/client'
import { createRouter } from './router'

const router = createRouter()

hydrateRoot(document, <RouterClient router={router} />)
// src/entry-client.tsx
import { hydrateRoot } from 'react-dom/client'
import { RouterClient } from '@tanstack/react-router/ssr/client'
import { createRouter } from './router'

const router = createRouter()

hydrateRoot(document, <RouterClient router={router} />)

通过这种设置,您的应用程序将在服务器上渲染,然后在客户端水合!

流式 SSR

流式 SSR 是最现代的 SSR 风格,是在服务器上渲染时持续增量地向客户端发送 HTML 标记的过程。这在概念上与传统 SSR 略有不同,因为除了能够脱水和重新水合关键的首次绘制外,优先级较低或响应时间较慢的标记和数据可以在初始渲染后但在同一请求中流式传输到客户端。

这种模式对于具有缓慢或高延迟数据获取要求的页面很有用。例如,如果您有一个需要从第三方 API 获取数据的页面,您可以将关键的初始标记和数据流式传输到客户端,然后在解析时将不太关键的第三方数据流式传输到客户端。

Note

只要您使用 defaultStreamHandlerrenderRouterToStream,这种流式传输模式就是完全自动的。

使用 defaultStreamHandler

tsx
// src/entry-server.tsx
import {
  createRequestHandler,
  defaultStreamHandler,
} from '@tanstack/react-router/ssr/server'
import { createRouter } from './router'

export async function render({ request }: { request: Request }) {
  const handler = createRequestHandler({ request, createRouter })

  return await handler(defaultStreamHandler)
}
// src/entry-server.tsx
import {
  createRequestHandler,
  defaultStreamHandler,
} from '@tanstack/react-router/ssr/server'
import { createRouter } from './router'

export async function render({ request }: { request: Request }) {
  const handler = createRequestHandler({ request, createRouter })

  return await handler(defaultStreamHandler)
}

使用 renderRouterToStream

tsx
// src/entry-server.tsx
import {
  createRequestHandler,
  renderRouterToStream,
  RouterServer,
} from '@tanstack/react-router/ssr/server'
import { createRouter } from './router'

export function render({ request }: { request: Request }) {
  const handler = createRequestHandler({ request, createRouter })

  return handler(({ request, responseHeaders, router }) =>
    renderRouterToStream({
      request,
      responseHeaders,
      router,
      children: <RouterServer router={router} />,
    }),
  )
}
// src/entry-server.tsx
import {
  createRequestHandler,
  renderRouterToStream,
  RouterServer,
} from '@tanstack/react-router/ssr/server'
import { createRouter } from './router'

export function render({ request }: { request: Request }) {
  const handler = createRequestHandler({ request, createRouter })

  return handler(({ request, responseHeaders, router }) =>
    renderRouterToStream({
      request,
      responseHeaders,
      router,
      children: <RouterServer router={router} />,
    }),
  )
}

流式脱水/水合

流式脱水/水合是一种超越标记的高级模式,允许您从服务器向客户端脱水和流式传输任何支持数据,并在到达时重新水合。这对于可能需要进一步使用/管理用于在服务器上渲染初始标记的底层数据的应用程序很有用。

数据序列化

使用 SSR 时,在服务器和客户端之间传递的数据必须在跨网络边界发送之前进行序列化。TanStack Router 使用一个非常轻量级的序列化器来处理这种序列化,该序列化器支持超越 JSON.stringify/JSON.parse 的常见数据类型。

开箱即用,支持以下类型:

  • undefined
  • Date
  • Error
  • FormData

如果您认为还有其他类型应该默认支持,请在 TanStack Router 存储库上开启一个 issue。

如果您使用更复杂的数据类型,如 MapSetBigInt 等,您可能需要使用自定义序列化器来确保您的类型定义准确,并且您的数据被正确序列化和反序列化。我们目前正在开发更强大的序列化器和为您的应用程序自定义序列化器的方法。如果您有兴趣帮助,请开启一个 issue!