Apollo Client React 服务端渲染 API 完全指南:从 `prerenderStatic` 到遗留 SSR 函数

发布时间:2026/9/20 21:34:10
Apollo Client React 服务端渲染 API 完全指南:从 `prerenderStatic` 到遗留 SSR 函数 Apollo Client React 服务端渲染 API 完全指南从prerenderStatic到遗留 SSR 函数【免费下载链接】apollo-clientThe industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.项目地址: https://gitcode.com/gh_mirrors/ap/apollo-client本文以仓库.api-reports/api-report-react_ssr.api.mdAPI Extractor 为apollo/clientReact SSR 子包生成的官方 API 报告为核心骨架结合 SSR 源码 与 官方 SSR 文档 深入讲解。读完你将掌握现代 SSR 函数prerenderStatic的完整参数与返回值语义、其内部重渲染直到没有网络请求的实现原理、如何在服务端渲染后提取并恢复 Apollo Client 缓存以及getDataFromTree、renderToStringWithData等遗留 API 的正确替代方案。一、这份 API 报告是什么.api-reports/api-report-react_ssr.api.md是由 API Extractor 自动生成的类型面TypeScript API surface快照精确记录了从apollo/client/react/ssr入口导出的全部公开符号、签名、public/deprecated标记以及泛型约束。对于开发者和使用 LLM/Agent 做代码检索的场景它是判断某个 SSR 函数是否仍被推荐使用、参数是什么的最权威依据之一。报告中一共出现了4 个函数 1 个命名空间可以划分为两代 API符号状态用途prerenderStaticpublic现行推荐反复渲染 React 树直到不再发起网络请求支持现代 React 渲染 APIgetDataFromTreepublic deprecated遗留渲染树并取回数据底层用renderToStaticMarkupgetMarkupFromTreepublic deprecated遗留getDataFromTree的可配置渲染函数版本renderToStringWithDatapublic deprecated遗留渲染树并返回字符串底层用renderToStringnamespace prerenderStaticpublic承载Options、Result、Diagnostics、PrerenderFunction等类型从报告中的(undocumented)标记也能看出遗留函数在 API 层面未附带额外文档注释——这正是官方将其标记为 deprecated、推荐统一迁移到prerenderStatic的直接信号。二、prerenderStatic现行推荐的 SSR 执行引擎报告给出了prerenderStatic的完整签名export function prerenderStatic Prerender extends prerenderStatic.PrerenderFunction prerenderStatic.PrerenderFunction ({ tree, context, renderFunction, signal, ignoreResults, diagnostics, maxRerenders, }: prerenderStatic.OptionsPrerender): PromiseprerenderStatic.ResultPrerender;它是泛型函数传入哪种renderFunction返回的renderFnResult就会带上对应的类型信息无需手动断言。2.1 参数Options全解依据 prerenderStatic.tsx 中的 Options 定义各参数语义如下参数类型必填说明treeReactTypes.ReactNode✅要预渲染的 React 组件树renderFunctionPrerender✅实际执行渲染的函数详见 2.2context{ client?: ApolloClient }否若你的应用没有被ApolloProvider包裹可在此传入client实例signalAbortSignal否中止信号指示提前停止重渲染循环即使数据未全部取完ignoreResultsboolean否为true时result返回省去把Uint8Array/Buffer转字符串的 CPU 开销diagnosticsboolean否为true时结果附带diagnostics默认false用于检测useQuery瀑布等低效渲染结构maxRerendersnumber否最大重渲染次数防止应用 bug 导致死循环默认50源码中关于maxRerenders的默认值是这样落地的maxRerenders 50,并在每次渲染开始时通过invariant做防爆检查超限会抛出明确错误信息提示要么是useQuery瀑布过深需要调大maxRerenders要么是应用存在无限渲染循环invariant( renderCount maxRerenders, Exceeded maximum rerender count of %d. ..., maxRerenders );2.2renderFunction的四种可选实现PrerenderFunction联合类型源码定义决定了你可以在哪些 React 渲染 API 之间切换类型对应 React API适用场景Suspense 支持RenderToStringrenderToStringreact-dom/server同步渲染最广兼容❌RenderToStringPromise返回 Promise 的字符串渲染自定义异步渲染视实现PrerenderToWebStreamprerenderreact-dom/staticDeno / 现代边缘运行时Web Streams✅PrerenderToNodeStreamprerenderToNodeStreamreact-dom/staticNode.js 环境✅官方文档 docs/source/performance/server-side-rendering.mdx 给出了选择建议prerenderWeb Streams推荐用于 Deno 或现代边缘运行时支持 React SuspenseprerenderToNodeStream推荐用于 Node.js支持 React SuspenserenderToString遗留 API不支持 Suspense无法配合useSuspenseQuery使用renderToStaticMarkup比renderToString略快但产出不可水合hydrate同样不支持 Suspense。⚠️ 注意renderToStaticMarkup与renderToString都属于react-dom/server由于不含 Suspense 支持无法与useSuspenseQuery、useBackgroundQuery等 suspenseful hooks 配合若你的应用使用了这些 hooks必须选择react-dom/static系列要求 React 版本满足react-dom/static可用性。2.3 返回值Resultexport interface ResultPrerender extends PrerenderFunction PrerenderFunction { aborted: boolean; // 是否因 AbortSignal 提前中止 diagnostics?: Diagnostics; // diagnostics: true 时存在 renderFnResult: ReturnTypePrerender extends PromiseLikeinfer U ? U : ReturnTypePrerender; result: string; // 最终 HTMLignoreResults 时为 }关键语义源码注释result最后一次渲染得到的 HTML 字符串ignoreResults: true时为空字符串aborted如果因signal取消而提前结束渲染则为true。此时若你使用的渲染函数支持水合除renderToStaticMarkup外都支持产出的result仍然可以在浏览器水合但其中可能残留loading状态需要浏览器端继续补取数据diagnostics开启后包含renderCount渲染次数数值偏高说明存在useQuery瀑布式串行请求可通过 fragment colocation 等手段优化Diagnostics 定义。三、底层原理它为什么能自动取完全部数据prerenderStatic的核心策略是反复渲染组件树直到没有任何新的网络请求被发起源码 prerenderStatic.tsx#L182-L194 的注释对此有明确说明若组件树只使用 suspenseful hooks如useSuspenseQuery配合支持 Suspense 的renderFunction树通常只渲染一次渲染期间挂起等待网络请求完成若使用非 suspense 的useQuery则渲染全部组件 → 等待本轮请求完成 → 再次渲染 → 直到没有新请求为止。这个机制在实现层面由三部分协作完成prerenderStatic.tsx#L213-L323注入内部 context通过ApolloContext.Provider把wrapperSymbol写入 context让useQuery在 SSR 期间被替换为useSSRQuery实现见 useSSRQuery.ts从而拦截每个查询的创建与结果读取跟踪 ObservableQuery每次渲染中新创建的、且fetchPolicy ! cache-only的ObservableQuery会被登记到recentlyCreatedObservableQueries用print(query) canonicalStringify(variables)生成的键去重缓存getObservableQueryKey等待数据就绪用Promise.all rxjs 的firstValueFrom等待所有新查询的loading false再进入下一轮渲染渲染期间保持对 ObservableQuery 的空订阅noopObserver防止其在轮次之间被销毁。安全阀有两道maxRerenders默认 50invariant断言防止无限循环signal通过Promise.race([abortPromise, dataPromise])让中止信号可以随时打断等待数据阶段并正确标记aborted: true。渲染结束后finally钩子会清理所有订阅与内部 Map避免内存泄漏prerenderStatic.tsx#L338-L343。四、实战完整的 Node.js SSR 渲染流程4.1 每个请求创建独立的 Apollo Client官方文档明确警告必须为每个请求创建全新的 Apollo Client 实例否则上一个请求的缓存可能泄漏给下一个用户server-side-rendering.mdx。// app.js import express from express; import { ApolloClient, InMemoryCache } from apollo/client; import { ApolloProvider } from apollo/client/react; import { StaticRouter } from react-router-dom/server; import { Layout } from ./Layout; const app express(); app.use(async (req, res) { const client new ApolloClient({ ssrMode: true, cache: new InMemoryCache(), uri: https://example.com/graphql, }); const context {}; const App ( ApolloProvider client{client} StaticRouter location{req.url} context{context} Layout / /StaticRouter /ApolloProvider ); // 用 prerenderStatic 执行全部查询见 4.2 });4.2 用prerenderStatic执行全部查询import { prerenderStatic } from apollo/client/react/ssr; import { prerenderToNodeStream } from react-dom/static; prerenderStatic({ tree: App, // App 已包含 ApolloProvider 时此参数可省略 context: { client }, renderFunction: prerenderToNodeStream, }).then(async ({ result }) { // 提取整个 Apollo Client 缓存的当前状态 const initialState client.extract(); // ... 发送响应见 4.3 });4.3 发送响应Node.js 流式方案Node.js 环境推荐用renderToPipeableStream流式下发浏览器可提前渲染首屏server-side-rendering.mdx#L172-L216import { renderToPipeableStream } from react-dom/server; prerenderStatic({ tree: App, context: { client }, renderFunction: prerenderToNodeStream, }).then(async ({ result }) { const initialState client.extract(); const { pipe } renderToPipeableStream( html headtitleMy App/title/head body div idrootApp //div /body /html, { bootstrapScriptContent: window.__APOLLO_STATE__${JSON.stringify( initialState ).replace(//g, \\u003c)}, bootstrapScripts: [/client.js], onShellReady() { res.setHeader(Content-Type, text/html); res.statusCode 200; pipe(res); }, onError(error) { console.error(Rendering error:, error); }, } ); });说明示例中的replace(//g, \\u003c)用于转义字符防止/script出现在字符串字面量中引发 XSS 攻击。4.4 客户端缓存恢复rehydration服务端把client.extract()的结果放进window.__APOLLO_STATE__后客户端创建 Apollo Client 时用restore恢复const client new ApolloClient({ cache: new InMemoryCache().restore(window.__APOLLO_STATE__), uri: https://example.com/graphql, });恢复后客户端首次查询命中缓存数据即时可用无需再次请求server-side-rendering.mdx#L334-L347。五、高级选项与部分预渲染Partial Prerendering5.1 诊断瀑布流const { diagnostics } await prerenderStatic({ tree: App, context: { client }, renderFunction: prerenderToNodeStream, diagnostics: true, }); console.log(Rendered ${diagnostics.renderCount} times); // renderCount 偏高时考虑用 fragment colocation 减少串行请求5.2 超时与提前中止const signal AbortSignal.timeout(2000); // 2 秒超时 const { result, aborted } await prerenderStatic({ tree: App, context: { client }, renderFunction: (tree) prerenderToNodeStream(tree, { signal }), signal, }); if (aborted) { console.log(Render timed out, returning partial result); }注意源码中的特别提示若你使用的prerender/prerenderToNodeStream本身就接受signal需要像上面这样在包裹的renderFunction里也把signal透传下去否则中止异常不会传播给prerenderStaticOptions.signal 的 JSDoc。5.3 深瀑布时调大maxRerendersawait prerenderStatic({ tree: App, context: { client }, renderFunction: prerenderToNodeStream, maxRerenders: 100, // 默认 50 });5.4prerenderresumeAndPrerender部分预渲染React 19.2.0 支持先用prerender预渲染公共数据如对所有用户相同的内容再用resumeAndPrerender恢复渲染用户私有数据server-side-rendering.mdx#L349-L431// 第一段预渲染公共部分 const controller new AbortController(); const promise prerenderStatic({ tree: App, context: { client }, renderFunction: (tree) prerender(tree, { signal: controller.signal }), signal: controller.signal, }); // 等可取数据就绪后中止保存中间产物 controller.abort(); const { result, renderFnResult, aborted } await promise; if (aborted) { const initialResponse result; // 到目前位置的 HTML const postponed renderFnResult.postponed; // 被延后的渲染工作 const cacheState client.extract(); // 当前缓存 }// 第二段恢复渲染用户私有部分 const client new ApolloClient({ cache: new InMemoryCache(), link }); client.cache.restore(cacheState); const controller new AbortController(); const promise prerenderStatic({ tree: App, context: { client }, renderFunction: (tree) resumeAndPrerender(tree, postponed, { signal: controller.signal }), signal: controller.signal, });注意resumeAndPrerender的result不包含此前渲染的 HTML需自行拼接多段渲染输出。六、遗留 APIgetDataFromTree系列API 报告将它们标记为deprecated (undocumented)官方文档也明确它们已被prerenderStatic取代后者对现代 React 渲染 API 提供了更好的灵活性与性能server-side-rendering.mdx#L488-L495。6.1getDataFromTreeexport function getDataFromTree( tree: ReactTypes.ReactNode, context?: { [key: string]: any } ): Promisestring;参数tree要渲染并取数的 React 树参数context可选作为渲染期间可用的 React Context 值返回值Promisestring数据就绪后解析底层使用ReactDOMServer.renderToStaticMarkupgetDataFromTree.ts#L10-L21结果不可水合用法import { getDataFromTree } from apollo/client/react/ssr;6.2getMarkupFromTreeexport function getMarkupFromTree({ tree, context, renderFunction, }: GetMarkupFromTreeOptions): Promisestring;renderFunction可选默认也是renderToStaticMarkup源码注释说明选择它是因为比renderToString更省开销且getDataFromTree的旧用法本就不关心返回值需要自定义渲染函数时应直接调用本函数而非getDataFromTreegetDataFromTree.ts#L36-L53。6.3renderToStringWithDataexport function renderToStringWithData( component: ReactTypes.ReactElementany ): Promisestring;与getDataFromTree类似但底层改用renderToString产出可水合标记因此适合需要水合的场景renderToStringWithData.ts。6.4 它们与prerenderStatic的关系从源码可以清楚看到三个遗留函数如今都只是prerenderStatic的薄封装getDataFromTree.ts、renderToStringWithData.ts// getMarkupFromTree 内部 const { result } await prerenderStatic({ tree, context, renderFunction, maxRerenders: Number.POSITIVE_INFINITY, // 遗留行为不设上限 }); return result;也就是说即便旧 API 也统一走新引擎的重渲染逻辑只是把maxRerenders设为无穷大、并固定绑定react-dom/server的旧式渲染函数——这正是迁移到prerenderStatic即可获得 Suspense 支持与maxRerenders保护的原因。七、与 SSR 配套的常用配置7.1ssrForceFetchDelay跳过初始化期的强制拉取若某些首屏查询使用network-only或cache-and-networkfetch policy可在客户端初始化时设置ssrForceFetchDelay毫秒让这些查询在初始化期间只走缓存const client new ApolloClient({ cache: new InMemoryCache().restore(window.__APOLLO_STATE__), link, ssrForceFetchDelay: 100, });7.2SchemaLink避免 SSR 期网络请求当 GraphQL 端点与渲染服务同机时可用SchemaLink代替HttpLink直接用 schema 与 context 执行查询不经过网络对localhost被防火墙拦截的环境尤其有用import { ApolloClient, InMemoryCache } from apollo/client; import { SchemaLink } from apollo/client/link/schema; const client new ApolloClient({ link: new SchemaLink({ schema }), cache: new InMemoryCache(), });7.3ssr: false选择性跳过 SSR 期查询对仅客户端可用的查询如依赖浏览器信息的用户数据设置ssr: false可避免在服务端执行。SSR 期间组件会收到loading: true、dataState: empty、data: undefined的结果水合后正常执行function ClientOnlyUser() { const { loading, data } useQuery(GET_USER_WITH_ID, { ssr: false }); if (loading) return spanLoading.../span; return spanUser: {data?.user?.name || Not loaded}/span; }与skip: true的区别skip: true在服务端与客户端都不执行且 SSR 期间返回loading: false、networkStatus: NetworkStatus.readyserver-side-rendering.mdx#L464-L486。八、测试保障仓库在 src/react/ssr/tests/prerenderStatic.test.tsx 中对prerenderStatic做了系统性的行为验证测试覆盖了分别使用prerenderWeb Streams与prerenderToNodeStreamNode Streams两种渲染函数配合useSuspenseQuery的真实 kitchen-sink场景MockLink模拟多级嵌套 Suspense 查询hello→whoami→currentTime三层瀑布maxRerenders防爆、signal中止、diagnostics.renderCount统计等选项行为。如果需要深入理解prerenderStatic在各种边界情况下的行为这份测试文件是最直接的参考资料。九、迁移建议你现在用的推荐迁移到收益getDataFromTreeprerenderStaticrenderToStaticMarkup获得maxRerenders保护、signal中止、diagnosticsrenderToStringWithDataprerenderStaticrenderToString同上且统一渲染入口getMarkupFromTreeprerenderStatic自定义renderFunction类型更精确泛型推断renderFnResult使用 Suspense hooks 的应用prerenderStaticreact-dom/staticprerender/prerenderToNodeStream支持 Suspense只渲染一轮性能更好迁移时只需把原来传给遗留函数的tree/context原样传入prerenderStatic并显式指定renderFunction即可若之前依赖无限重渲染行为需按需调大maxRerenders。参考资料仓库内路径API 报告SSR 入口导出prerenderStatic 实现useSSRQuery 实现遗留函数实现React API 文档SSR 完整指南测试用例【免费下载链接】apollo-clientThe industry-leading GraphQL client for TypeScript, JavaScript, React, Vue, Angular, and more. Apollo Client delivers powerful caching, intuitive APIs, and comprehensive developer tools to accelerate your app development.项目地址: https://gitcode.com/gh_mirrors/ap/apollo-client创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考