服务器端渲染 (SSR) 是在服务器上渲染组件并将 HTML 标记发送到客户端的过程。然后客户端将标记补水为完全交互式的组件。
通常需要考虑两种不同的 SSR 风格
本指南将解释如何使用 TanStack Router 实现这两种 SSR 风格!
非流式服务器端渲染是在服务器上渲染整个应用程序页面的标记,并将完成的 HTML 标记(和数据)发送到客户端的经典过程。然后客户端再次将标记补水为完全交互式的应用程序。
要使用 TanStack Router 实现非流式 SSR,您将需要以下实用程序
由于您的路由器将同时存在于服务器和客户端上,因此重要的是您以在这些环境之间保持一致的方式创建路由器。 最简单的方法是在共享文件中公开一个 createRouter 函数,该函数可以由您的服务器和客户端入口文件导入和调用。
import * as React from 'react'
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>
}
}
import * as React from 'react'
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>
}
}
现在,您可以在服务器和客户端入口文件中导入此函数并创建路由器。
import { createRouter } from './router'
export async function render(req, res) {
const router = createRouter()
}
import { createRouter } from './router'
export async function render(req, res) {
const router = createRouter()
}
import { createRouter } from './router'
const router = createRouter()
import { createRouter } from './router'
const router = createRouter()
在客户端,Router 默认使用 createBrowserHistory 的实例,这是客户端上首选的历史记录类型。 但是,在服务器上,您将希望使用 createMemoryHistory 的实例。 这是因为 createBrowserHistory 使用 window 对象,该对象在服务器上不存在。
🧠 确保使用正在渲染的服务器 URL 初始化您的内存历史记录。
const router = createRouter()
const memoryHistory = createMemoryHistory({
initialEntries: [opts.url],
})
const router = createRouter()
const memoryHistory = createMemoryHistory({
initialEntries: [opts.url],
})
创建内存历史记录实例后,您可以更新路由器以使用它。
router.update({
history: memoryHistory,
})
router.update({
history: memoryHistory,
})
为了在服务器上渲染您的应用程序,您需要确保路由器已通过其路由加载器加载了任何关键数据。 为此,您可以在渲染应用程序之前 await router.load()。 这将完全等待为此 URL 找到的每个匹配路由运行其路由的 loader 函数(并行)。
await router.load()
await router.load()
只要您完成本指南中概述的标准 SSR 步骤,路由获取的已解析加载器数据就会由 TanStack Router 自动脱水和补水。
⚠️ 如果您正在使用延迟数据流式处理,您还需要确保您已实施本指南末尾附近的 SSR 流式处理和流转换 模式。
有关如何利用数据加载和数据流式处理的更多信息,请参阅数据加载和数据流式处理指南。
现在您有了一个路由器实例,它已加载当前 URL 的所有关键数据,您可以在服务器上渲染您的应用程序
// src/entry-server.tsx
const html = ReactDOMServer.renderToString(<StartServer router={router} />)
// src/entry-server.tsx
const html = ReactDOMServer.renderToString(<StartServer router={router} />)
router 有一个方法 hasNotFoundMatch 来检查在渲染过程中是否发生了未找到错误。 使用此方法检查是否发生了未找到错误,并相应地设置响应状态代码
// src/entry-server.tsx
if (router.hasNotFoundMatch()) statusCode = 404
// src/entry-server.tsx
if (router.hasNotFoundMatch()) statusCode = 404
这是一个完整的服务器入口文件示例,其中使用了上面讨论的所有概念。
// src/entry-server.tsx
import * as React from 'react'
import ReactDOMServer from 'react-dom/server'
import { createMemoryHistory } from '@tanstack/react-router'
import { StartServer } from '@tanstack/react-start/server'
import { createRouter } from './router'
export async function render(url, response) {
const router = createRouter()
const memoryHistory = createMemoryHistory({
initialEntries: [url],
})
router.update({
history: memoryHistory,
})
await router.load()
const appHtml = ReactDOMServer.renderToString(<StartServer router={router} />)
response.statusCode = router.hasNotFoundMatch() ? 404 : 200
response.setHeader('Content-Type', 'text/html')
response.end(`<!DOCTYPE html>${appHtml}`)
}
// src/entry-server.tsx
import * as React from 'react'
import ReactDOMServer from 'react-dom/server'
import { createMemoryHistory } from '@tanstack/react-router'
import { StartServer } from '@tanstack/react-start/server'
import { createRouter } from './router'
export async function render(url, response) {
const router = createRouter()
const memoryHistory = createMemoryHistory({
initialEntries: [url],
})
router.update({
history: memoryHistory,
})
await router.load()
const appHtml = ReactDOMServer.renderToString(<StartServer router={router} />)
response.statusCode = router.hasNotFoundMatch() ? 404 : 200
response.setHeader('Content-Type', 'text/html')
response.end(`<!DOCTYPE html>${appHtml}`)
}
在客户端,事情要简单得多。
// src/entry-client.tsx
import * as React from 'react'
import ReactDOM from 'react-dom/client'
import { StartClient } from '@tanstack/react-start'
import { createRouter } from './router'
const router = createRouter()
ReactDOM.hydrateRoot(document, <StartClient router={router} />)
// src/entry-client.tsx
import * as React from 'react'
import ReactDOM from 'react-dom/client'
import { StartClient } from '@tanstack/react-start'
import { createRouter } from './router'
const router = createRouter()
ReactDOM.hydrateRoot(document, <StartClient router={router} />)
通过此设置,您的应用程序将在服务器上渲染,然后在客户端上补水!
流式 SSR 是最现代的 SSR 风格,是在服务器上渲染时持续且增量地将 HTML 标记发送到客户端的过程。 这在概念上与传统的 SSR 略有不同,因为除了能够脱水和补水关键的首次绘制之外,优先级较低或响应时间较慢的标记和数据可以在初始渲染之后但在同一请求中流式传输到客户端。
这种模式对于具有缓慢或高延迟数据获取要求的页面非常有用。 例如,如果您的页面需要从第三方 API 获取数据,您可以将关键的初始标记和数据流式传输到客户端,然后将不太关键的第三方数据在解析后流式传输到客户端。
只要您使用 renderToPipeableStream,这种流式处理模式都是自动的.
流式脱水/补水是一种高级模式,它超越了标记,允许您从服务器脱水和流式传输任何支持数据到客户端,并在到达时对其进行补水。 这对于可能需要进一步使用/管理用于在服务器上渲染初始标记的底层数据的应用程序很有用。
使用 SSR 时,在服务器和客户端之间传递的数据必须在跨网络边界发送之前进行序列化。 TanStack Router 使用非常轻量级的序列化器处理此序列化,该序列化器支持 JSON.stringify/JSON.parse 之外的常见数据类型。
开箱即用,支持以下类型
如果您认为默认情况下应该支持其他类型,请在 TanStack Router 存储库上打开一个 issue。
如果您使用更复杂的数据类型,如 Map、Set、BigInt 等,您可能需要使用自定义序列化器以确保您的类型定义是准确的,并且您的数据已正确序列化和反序列化。 我们目前正在开发更强大的序列化器以及自定义应用程序序列化器的方法。 如果您有兴趣提供帮助,请打开一个 issue!
数据序列化 API 允许使用自定义序列化器,该序列化器可以让我们在跨网络通信时透明地使用这些数据类型。
import { SuperJSON } from 'superjson'
const router = createRouter({
serializer: SuperJSON,
})
import { SuperJSON } from 'superjson'
const router = createRouter({
serializer: SuperJSON,
})
就像这样,TanStack Router 现在将适当地使用 SuperJSON 来序列化跨网络的数据。
您的每周 JavaScript 新闻速递。 每周一免费发送给超过 100,000 名开发人员。