SSR

警告

尽管已尽一切努力将这些 API 与 Tanstack Start 的更改分开,但内部存在共享实现的底层。因此,这些可能会发生变化,在 Start 达到稳定状态之前,应将其视为实验性的。

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

通常需要考虑两种不同的 SSR 形式:

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

本指南将介绍如何使用 TanStack Router 实现这两种 SSR 形式!

非流式 SSR

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

要使用 TanStack Router 实现非流式 SSR,您将需要以下实用工具:

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

自动服务器历史记录

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

自动加载器脱水/水合

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

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

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

路由创建

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

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

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

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

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

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

在服务器上渲染应用程序

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

使用 defaultRenderToString

tsx
// src/entry-server.tsx
import {
  createRequestHandler,
  defaultRenderToString,
} from '@tanstack/solid-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/solid-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/solid-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/solid-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 { hydrate } from 'solid-js/web'
import { RouterClient } from '@tanstack/solid-router/ssr/client'
import { createRouter } from './router'

const router = createRouter()

hydrate(() => <RouterClient router={router} />, document.body)
// src/entry-client.tsx
import { hydrate } from 'solid-js/web'
import { RouterClient } from '@tanstack/solid-router/ssr/client'
import { createRouter } from './router'

const router = createRouter()

hydrate(() => <RouterClient router={router} />, document.body)

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

流式 SSR

流式 SSR 是最现代的 SSR 形式,它是在服务器上渲染 HTML 标记并随着渲染过程不断、增量地将标记发送到客户端的过程。这在概念上与传统 SSR 略有不同,因为除了能够对首屏关键标记和数据进行脱水和水合外,响应时间较慢或优先级较低的标记和数据也可以在初始渲染后但在同一个请求中流式传输到客户端。

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

注意

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

使用 defaultStreamHandler

tsx
// src/entry-server.tsx
import {
  createRequestHandler,
  defaultStreamHandler,
} from '@tanstack/solid-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/solid-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/solid-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/solid-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!

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

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

Bytes

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

订阅 Bytes

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

Bytes

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