执行契约
- 适用场景
- Router 或 Start 应用要在 SSR 中预取 Query,并自动脱水、恢复或流式发送结果。
- 不要使用
- 纯 SPA 或仅客户端数据不需要 SSR 序列化;不要为它们增加服务端恢复复杂度。
- 前置检查
- 确认 getRouter 按请求执行、QueryClient 的所有者、关键与非关键查询分类,以及允许进入浏览器载荷的数据字段。
- 验证结果
- 用并发用户会话检查缓存隔离,检查 HTML/流载荷不含秘密,并确认 hydrated 查询不会立刻重复请求。
- 失败模式
- 跨请求数据串线说明 QueryClient 被提升成模块单例;hydration 后重复请求通常来自 key、实例或 staleTime 不一致。
- 安全边界
- 脱水缓存对浏览器可见;先授权再查询,只序列化最小字段,并依赖安全转义的官方集成处理 HTML 上下文。
实例所有权
QueryClient 必须创建在 getRouter 内并同时传给 Router Context 与集成函数。不得把带用户数据的 QueryClient 提升成服务端模块单例。SSR 查询设置合理 staleTime,避免恢复后立即因为陈旧状态重复请求。
export function getRouter() {
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 30_000 } },
})
const router = createRouter({ routeTree, context: { queryClient } })
setupRouterSsrQueryIntegration({ router, queryClient })
return router
}加载模式
关键数据在 Loader 中 return/await ensureQueryData;允许渐进到达的数据只启动 fetchQuery,不 return 或 await。组件需要参与 SSR 时使用 useSuspenseQuery;普通 useQuery 在 hydration 后才从客户端请求。避免顺序 await 无依赖的查询。
载荷审计
只脱水允许浏览器读取的数据,可用 shouldDehydrateQuery 排除明确标记的条目。检查 HTML 与流式载荷不含秘密、服务端字段或其他用户数据;自定义序列化必须处理 HTML 转义,不要直接拼接 JSON.stringify。