框架
版本

SSR

警告

尽管我们已尽力将这些 API 与 Tanstack Start 的更改分离开来,但内部存在共享的底层实现。因此,这些可能会有所更改,在 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 获取数据,您可以将关键的初始标记和数据流式传输到客户端,然后将不太关键的第三方数据在解析后流式传输到客户端。

注意

只要您使用 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 存储库中提出问题。

如果您正在使用更复杂的数据类型,例如 MapSetBigInt 等,您可能需要使用自定义序列化器来确保您的类型定义准确无误,并且您的数据正确序列化和反序列化。我们目前正在开发更健壮的序列化器和一种为您的应用程序自定义序列化器的方法。如果您有兴趣提供帮助,请提出问题!

我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。