以下是翻译后的中文文档,保持所有代码块、Markdown格式、HTML标签和变量不变:
服务端渲染(Server Side Rendering,SSR)是指在服务器端渲染组件并将HTML标记发送至客户端的过程。客户端随后将这些标记水合(hydrate)为完全交互式的组件。
通常需要考虑两种不同的SSR模式:
本指南将说明如何使用TanStack Router实现这两种SSR模式!
非流式服务端渲染是传统模式,即在服务器端渲染整个应用页面的标记,并将完整的HTML标记(及数据)发送至客户端。客户端随后将这些标记水合为完全交互式的应用。
要实现非流式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实例,这是在客户端首选的history类型。但在服务器端,你需要改用createMemoryHistory实例,因为createBrowserHistory依赖window对象,而服务器端不存在此对象。
🧠 确保使用正在渲染的服务器URL初始化内存history。
const router = createRouter()
const memoryHistory = createMemoryHistory({
initialEntries: [opts.url],
})
const router = createRouter()
const memoryHistory = createMemoryHistory({
initialEntries: [opts.url],
})
创建内存history实例后,可以更新路由以使用它。
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来序列化跨网络的数据。
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.