Warning
虽然已经尽力将这些 API 与 Tanstack Start 的变更分离,但内部仍有底层共享实现。因此,这些 API 可能会发生变化,在 Start 达到稳定状态之前应被视为实验性的。
服务器端渲染(SSR)是在服务器上渲染组件并将 HTML 标记发送到客户端的过程。然后客户端将标记水合为完全交互式的组件。
通常有两种不同的 SSR 风格需要考虑:
本指南将解释如何使用 TanStack Router 实现这两种 SSR 风格!
非流式服务器端渲染是在服务器上为整个应用程序页面渲染标记并将完整的 HTML 标记(和数据)发送到客户端的经典过程。然后客户端将标记水合为完全交互式的应用程序。
要使用 TanStack Router 实现非流式 SSR,您需要以下实用程序:
在客户端,Router 默认使用 createBrowserHistory 的实例,这是在客户端使用的首选历史记录类型。但是,在服务器上,您需要使用 createMemoryHistory 的实例。这是因为 createBrowserHistory 使用 window 对象,而该对象在服务器上不存在。这在 RouterServer 组件中会自动为您处理。
只要您完成本指南中概述的标准 SSR 步骤,路由获取的已解析加载器数据就会被 TanStack Router 自动脱水和重新水合。
⚠️ 如果您使用延迟数据流,您还需要确保已实现本指南末尾附近的 SSR 流式传输和流转换 模式。
有关如何利用数据加载的更多信息,请参阅 数据加载 指南。
由于您的路由器将同时存在于服务器和客户端,因此重要的是以在这两个环境之间保持一致的方式创建路由器。最简单的方法是在共享文件中公开一个 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>
}
}
// 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
// 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
// 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 对象的服务器框架,您需要从一个转换到另一个。请查看示例以了解这样的实现可能是什么样子。
在客户端,事情要简单得多。
// 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 风格,是在服务器上渲染时持续增量地向客户端发送 HTML 标记的过程。这在概念上与传统 SSR 略有不同,因为除了能够脱水和重新水合关键的首次绘制外,优先级较低或响应时间较慢的标记和数据可以在初始渲染后但在同一请求中流式传输到客户端。
这种模式对于具有缓慢或高延迟数据获取要求的页面很有用。例如,如果您有一个需要从第三方 API 获取数据的页面,您可以将关键的初始标记和数据流式传输到客户端,然后在解析时将不太关键的第三方数据流式传输到客户端。
Note
只要您使用 defaultStreamHandler 或 renderRouterToStream,这种流式传输模式就是完全自动的。
使用 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)
}
// 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
// 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 的常见数据类型。
开箱即用,支持以下类型:
如果您认为还有其他类型应该默认支持,请在 TanStack Router 存储库上开启一个 issue。
如果您使用更复杂的数据类型,如 Map、Set、BigInt 等,您可能需要使用自定义序列化器来确保您的类型定义准确,并且您的数据被正确序列化和反序列化。我们目前正在开发更强大的序列化器和为您的应用程序自定义序列化器的方法。如果您有兴趣帮助,请开启一个 issue!