首页
/ tRPC v9 服务端数据预取指南:全面掌握 createSSGHelpers 与 Next.js SSG/SSR 集成

tRPC v9 服务端数据预取指南:全面掌握 createSSGHelpers 与 Next.js SSG/SSR 集成

2026-09-08 15:25:37作者:彭桢灵Jeremy

本文聚焦 tRPC(以仓库中 version-9.x SSG Helpers 文档 为主体)在 v9 时代提供的关键服务端工具 createSSGHelpers,讲解如何借助它在 Next.js 的 getServerSideProps / getStaticProps 阶段提前执行 tRPC 查询、序列化 React Query 缓存并完成客户端水合(hydration)。读完你将能够:理解 prefetchQueryfetchQuerydehydrate 等辅助函数各自的职责与差异,写出可运行的 SSR/SSG 页面,并理清该 API 在 tRPC v9 → v10 → v11 演进中的对应关系。

createSSGHelpers:在服务端为 React Query 预取数据的入口

createSSGHelpers(全称 Server-Side Generation Helpers,即"服务端生成辅助函数")是 tRPC v9 提供给 Next.js 等 SSR/SSG 场景的桥接工具。它的核心作用正如官方文档开篇所述:

createSSGHelpers provides you a set of helper functions that you can use to prefetch queries on the server.

在浏览器端,页面通过 useQuery 发起 tRPC 请求;而服务端渲染或静态生成阶段没有浏览器、没有 React Query 的运行时挂载,因此需要一种"在服务端先把数据请求执行完,再把结果打包交给客户端复用"的手段。createSSGHelpers 正是负责这件事:它接收你的 router、context 以及可选的数据 transformer,返回一组经过类型约束的辅助函数,这些函数内部全部是对 react-query 方法的封装。

基础调用形态

官方文档给出的最小初始化代码如下(出处:ssg-helpers.md):

import { createSSGHelpers } from '@trpc/react/ssg';

const {
  prefetchQuery,
  prefetchInfiniteQuery,
  fetchQuery,
  fetchInfiniteQuery,
  dehydrate,
  queryClient,
} = await createSSGHelpers({
  router: appRouter,
  ctx: createContext,
  transformer: superjson, // optional - adds superjson serialization
});

几点需要注意:

  • await 语义createSSGHelpers 在 v9 中是异步工厂,ctx 既可传 createContext 函数本身,也可传 await createContext() 之后的 context 对象(见下文 Next.js 示例,两种写法文档中都有出现)。
  • router:传入应用的根路由(如 appRouter),它决定了后续字符串形式的 procedure 路径(如 'post.byId')能被强类型校验。
  • ctx:服务端 context。若页面需要鉴权、数据库访问等上下文,应在此传入与运行时一致的创建方式。
  • transformer(可选):如 superjson,用于序列化 DateMapSet 等 JSON 无法直接表达的类型。必须与服务端 HTTP 处理器、客户端 links 中配置的 transformer 保持一致,否则反序列化会错位。

返回的六个成员各自用途

返回值 类型/职责 关键差异
prefetchQuery 预取普通查询 不返回查询结果,只是把数据写入服务端 QueryClient 缓存,供 dehydrate() 打包
fetchQuery 取回普通查询 返回查询结果本身,适合既要在服务端拿到值做逻辑(如 404 判断)又想缓存到客户端的场景
prefetchInfiniteQuery 预取无限滚动查询(配合 useInfiniteQuery 需要按页面参数形式传入,缓存的是分页结构
fetchInfiniteQuery 取回无限滚动查询 返回多页数据,供服务端消费
dehydrate 序列化整个 React Query 缓存 输出可塞入 props.trpcState 的普通对象,随页面 HTML 一起下发
queryClient 暴露底层 react-query 的 QueryClient 需要执行更底层操作(如 setQueryData)时可直接使用

官方文档特别强调:所有返回函数本质上都是 react-query 对应方法的薄封装,因此其缓存键语义、失效规则、重试机制均继承自 react-query。这一点在仓库当前的测试中依然有直接体现,例如 ssg.test.ts 中围绕 createSSGHelpers(v11 时代的同名 API)验证了查询键与脱水缓存的一致性。

在 getServerSideProps 中使用:完整 SSR 示例拆解

文档给出了一个非常典型的"文章详情页"示例,其流程代表了 SSR 场景的完整数据管线。我们逐段剖析:

import { createSSGHelpers } from '@trpc/react/ssg';
import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import { createContext, prisma } from 'server/context';
import { appRouter } from 'server/routers/_app';
import superjson from 'superjson';
import { trpc } from 'utils/trpc';

export async function getServerSideProps(
  context: GetServerSidePropsContext<{ id: string }>,
) {
  const ssg = createSSGHelpers({
    router: appRouter,
    ctx: await createContext(),
    transformer: superjson,
  });
  const id = context.params?.id as string;

  /*
   * Prefetching the `post.byId` query here.
   * `prefetchQuery` does not return the result - if you need that, use `fetchQuery` instead.
   */
  await ssg.prefetchQuery('post.byId', {
    id,
  });

  // Make sure to return { props: { trpcState: ssg.dehydrate() } }
  return {
    props: {
      trpcState: ssg.dehydrate(),
      id,
    },
  };
}

export default function PostViewPage(
  props: InferGetServerSidePropsType<typeof getServerSideProps>,
) {
  const { id } = props;

  // This query will be immediately available as it's prefetched.
  const postQuery = trpc.useQuery(['post.byId', { id }]);

  const { data } = postQuery;

  return (
    <>
      <h1>{data.title}</h1>
      <em>Created {data.createdAt.toLocaleDateString()}</em>

      <p>{data.text}</p>

      <h2>Raw data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    </>
  );
}

三步走的数据流

  1. 初始化 helpers:在 getServerSideProps 中创建 ssg。这里 ctx: await createContext() 与基础示例中直接传 ctx: createContext 的区别在于:你可以先拿到 context(例如完成鉴权后的用户信息)再继续后续逻辑。
  2. 预取数据await ssg.prefetchQuery('post.byId', { id })。v9 使用字符串路径 + 输入参数的调用风格(注意 v9 中还可用数组写法,如第 72 行客户端侧的 trpc.useQuery(['post.byId', { id }]))。路径与入参都被 router: appRouter 约束,写错会直接得到 TypeScript 报错。此调用在服务端真正执行了数据库/HTTP 调用,把结果写入缓存,但不向调用方返回结果
  3. 脱水并下发props 中必须包含 trpcState: ssg.dehydrate()dehydrate() 把 React Query 缓存序列化为普通对象,随 Next.js 的 props 进入页面 HTML;客户端挂载时,withTRPC/createTRPCNext 包装层会自动读取该 trpcState 作为对应 useQuery 的初始数据。这也是文档注释 "Make sure to return { props: { trpcState: ssg.dehydrate() } }" 想强调的约定——漏掉 trpcState,预取就白做了

客户端为何"立即可用"

第 71-72 行的注释说明了水合后的体验:"This query will be immediately available as it's prefetched"。因为服务端已经把 post.byId 的数据连同查询键一起脱水,浏览器端同名 useQuery(['post.byId', { id }]) 会命中这份初始缓存,页面首屏渲染即展示数据,无需等待新的网络往返;组件里 data.titledata.createdAt 之所以能直接使用,是因为 superjson 保真了 createdAtDate 类型。

prefetch 与 fetch 的选择:何时用 fetchQuery

文档在示例注释中点明了两者的分界:

prefetchQuery does not return the result - if you need that, use fetchQuery instead.

换句话说:

  • 只想"暖缓存"给客户端 → 用 prefetchQuery,服务端不关心返回值;
  • 服务端需要基于查询结果做分支逻辑(例如数据不存在时 return { notFound: true }、或把结果加工后额外写入 props)→ 用 fetchQuery 拿到真实数据;
  • 分页/无限加载数据 → 对应使用 prefetchInfiniteQuery / fetchInfiniteQuery

dehydrate 的作用对象是整个 QueryClient 缓存,因此即使你在服务端同时预取了多条查询(普通 + 无限),一次 dehydrate() 也能把它们全部打包下发,客户端各条 useQuery / useInfiniteQuery 会自动按查询键取回各自初始数据。

静态生成(SSG)场景:createSSGHelpers + getStaticProps

createSSGHelpers 不只服务 SSR,也是 v9 实现静态站点生成的关键(见同目录下的配套文档 ssg.md)。思路与 SSR 完全相同,只是把调用点换到 getStaticProps,并配合 getStaticPaths 枚举动态路由:

import { createSSGHelpers } from '@trpc/react/ssg';
import {
  GetStaticPaths,
  GetStaticPropsContext,
  InferGetStaticPropsType,
} from 'next';
import { prisma } from 'server/context';
import { appRouter } from 'server/routers/_app';
import superjson from 'superjson';
import { trpc } from 'utils/trpc';

export async function getStaticProps(
  context: GetStaticPropsContext<{ id: string }>,
) {
  const ssg = await createSSGHelpers({
    router: appRouter,
    ctx: {},
    transformer: superjson, // optional - adds superjson serialization
  });
  const id = context.params?.id as string;

  // prefetch `post.byId`
  await ssg.fetchQuery('post.byId', {
    id,
  });

  return {
    props: {
      trpcState: ssg.dehydrate(),
      id,
    },
    revalidate: 1,
  };
}

export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await prisma.post.findMany({
    select: {
      id: true,
    },
  });

  return {
    paths: posts.map((post) => ({
      params: {
        id: post.id,
      },
    })),
    // https://nextjs.org/docs/basic-features/data-fetching#fallback-blocking
    fallback: 'blocking',
  };
};

export default function PostViewPage(
  props: InferGetStaticPropsType<typeof getStaticProps>,
) {
  const { id } = props;
  const postQuery = trpc.useQuery(['post.byId', { id }]);

  if (postQuery.status !== 'success') {
    // won't happen since we're using `fallback: "blocking"`
    return <>Loading...</>;
  }
  const { data } = postQuery;
  return (
    <>
      <h1>{data.title}</h1>
      <em>Created {data.createdAt.toLocaleDateString('en-us')}</em>

      <p>{data.text}</p>

      <h2>Raw data:</h2>
      <pre>{JSON.stringify(data, null, 4)}</pre>
    </>
  );
}

此例可以读出几个 SSG 专属要点:

  • ctx{} 即可:静态生成发生在构建期,无真实请求,因此没有 HTTP 层面的 context;除非路由确实需要(如基于环境变量的数据源),直接传空对象即可。
  • revalidate: 1 开启 ISR:让页面在增量静态再生成模式下周期性刷新,适合内容会变化又不想退回 SSR 的场景。
  • fallback: 'blocking' 与状态兜底:因为构建期未枚举到的路径会走服务端阻塞式生成,客户端理论上必然已具备数据,因此组件中 status !== 'success' 的分支只是防御性兜底(这正是 ssg.md 中注释 "won't happen since we're using fallback: 'blocking'" 的含义)。
  • 配套的真实实现可参考仓库示例应用 examples/next-prisma-todomvc,它使用 Prisma + superjson,完整演示了这类页面在 src/pages 与服务端路由之间的协作方式。

需要留意的 react-query 行为:默认会再次请求

值得强调的是:SSG/SSR 预取只是首屏初始值。react-query 的默认策略是在客户端挂载后重新验证(refetch)数据。若你的页面希望"完全以服务端数据为准、避免重复请求",需要关闭相关重取开关——在 v9→v10 迁移后的文档 client/nextjs/ssg.md 中对此有明确说明:

  • 按查询关闭:trpc.example.useQuery(undefined, { refetchOnMount: false, refetchOnWindowFocus: false })(注意:无输入参数的查询,别把选项对象误当作第一个参数传入);
  • 全局关闭:在 createTRPCNextconfig 中通过 queryClientConfig.defaultOptions.queries 统一设置。

该提示尤其适用于对接第三方限流 API 的场景——每增加一次客户端重复请求就多消耗一次配额。若页面同时混合静态与动态查询,则建议按查询而非全局配置处理,以免影响动态数据的实时性。

API 的版本演进:从 v9 到今天

理解 createSSGHelpers 时容易与仓库当前主版本(v11)混淆,因此有必要厘清它在版本线中的演变。仓库迁移文档 migrate-from-v10-to-v11.mdx 的 "SSG Helpers" 一节给出了权威说明:

  • v9:导出 createSSGHelpers(即本文主体,来源为 @trpc/react/ssg),采用字符串路径 + 数组参数的调用风格(如 ssg.prefetchQuery('post.byId', { id }));
  • v10:v9 的 createSSGHelpers 被移除,改用基于 Proxy 的 createProxySSGHelpers,调用风格升级为 helpers.post.byId.prefetch({ id }) 的对象链式写法;
  • v11:createProxySSGHelpers 更名为 createSSGHelpers(旧 Proxy 名保留为向后兼容的别名),且 v10/v11 的导出位置为 @trpc/react-query/server,函数名为 createServerSideHelpers(见 v10 版 ssg 文档 的示例)。

所以,若你正在阅读 v9 时代的文档或存量代码,请把 @trpc/react/ssg 与字符串路径调用理解为 v9 专属形态;升级时只需按上述映射关系平移 API 名称与调用风格,底层"服务端预取 → dehydratetrpcState 下发 → 客户端水合"的数据流思想完全一致。

从当前仓库源码结构看,这一能力在现代实现中位于 packages/react-query/src/server/ssgProxy.ts(以 Proxy 形式为每个 procedure 生成 .prefetch / .fetch 等方法),并有 ssg.test.tsprefetchQuery.test.tsxdehydrate.test.tsx 等测试佐证其脱水与预取语义,感兴趣的读者可以顺藤摸瓜做源码级阅读。

小结

createSSGHelpers 是 tRPC v9 在 Next.js SSR/SSG 场景下的核心工具:它让你在服务端用类型安全的方式预取任意 procedure,把 React Query 缓存通过 dehydrate() 序列化进 props.trpcState,最终由客户端无缝接管。掌握它的关键在于三点:

  1. 调用约定router + ctx + 可选 transformer 初始化;prefetchQuery 不返回值、fetchQuery 返回值,按服务端是否需要消费结果来选用;
  2. 水合协议:务必在 props 中携带 trpcState: ssg.dehydrate(),客户端同名 useQuery 才能拿到首屏初始数据;
  3. 版本意识:v9 的字符串路径 API 在 v10/v11 已演进为 Proxy 式链式 API,迁移对照表见 migrate-from-v10-to-v11.mdx,配合 ssg.md 可进一步了解 getStaticProps 侧的完整用法。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525