ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

tRPC Server-Side Helpers 完全指南:在 Next.js 中服务端预取查询并水合页面

tRPC Server-Side Helpers 完全指南:在 Next.js 中服务端预取查询并水合页面 tRPC Server-Side Helpers 完全指南在 Next.js 中服务端预取查询并水合页面【免费下载链接】trpc‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc本文基于本仓库 tRPC v10 文档 www/versioned_docs/version-10.x/client/nextjs/server-side-helpers.md 整理而成并对照仓库源码与配套文档进行深度展开。适合使用 Next.js Pages Router trpc/react-query 的开发者用于在getStaticPropsSSG或getServerSidePropsSSR中预取数据。Server-Side Helpers 是 tRPC 为 Next.js 提供的一组服务端辅助函数让你能在渲染发生在客户端之前先在服务端把查询填充进 Query Cache 并完成水合从而避免浏览器首次加载时再发一次网络请求。本文将从 API 形态、两种初始化方式内部 Router / 外部 Router、prefetch/fetch等方法的语义差异一直深入到源码级实现最后给出可复制的 Pages Router 完整示例。什么是 Server-Side Helpers在 tRPC v10 的 Next.js 集成中页面数据有几种获取方式开启ssr: true让 tRPC 在服务端渲染时自动预取全部查询或保持ssr关闭默认改用Server-Side Helpers在getStaticProps或getServerSideProps中手动预取查询。使用 Server-Side Helpers 预取的核心价值在于它把查询结果填充到服务端的 Query Cache 中随后这些查询在客户端首次挂载时不必再发请求而是直接消费水合后的缓存数据。createServerSideHelpers返回的对象结构与 tRPC 客户端非常相似同样以你的 Router 层级作为键但方法不再是useQuery/useMutation这类 Hook而是替换成了prefetch、fetch、prefetchInfinite、fetchInfinite这些可被直接await的普通函数。这与 packages/react-query/src/server/ssgProxy.ts 中声明的SSGFns集合完全一致v10 中还包括queryOptions/infiniteQueryOptions两个辅助方法。两种创建方式内部 Router 与外部 RouterServer-Side Helpers 有两种初始化方式取决于你是否能直接拿到 tRPC Router 实例。方式一内部 Routermonolith 架构这种方式适用于你能直接访问 Router 的场景例如一个单体 Next.js 应用前后端运行在同一个进程里import { createServerSideHelpers } from trpc/react-query/server; import { createContext } from ~/server/context; import superjson from superjson; const helpers createServerSideHelpers({ router: appRouter, ctx: await createContext(), transformer: superjson, // 可选 - 增加 superjson 序列化 });使用 Helpers 时tRPC 会直接调用服务端的 procedure而不会发出 HTTP 请求其语义与 服务端调用server-side calls 相同。这带来一个关键差异你手上没有req与res。因此请务必使用一个不含req/res的 context 来初始化 Helpers——而这两个字段通常正是由你的 context 创建逻辑填充的。官方建议在这种情况下采用「inner / outer context」的分层设计把与请求无关的依赖如数据库连接、Session 校验等放进 inner context参见 context 文档。从源码看走内部 Router 路径时ssgProxy.ts 会进入router in opts分支把预取操作映射为对callProcedure的直接调用——输入由getRawInput提供、类型固定为query、signal为undefined这印证了「不经 HTTP、不经请求头」的调用特征。方式二外部 Router前后端分离当你无法直接访问 Router 时——例如 Next.js 应用与独立部署的后端 API 分离——可以改为传入一个tRPC Proxy Client让 Helpers 通过网络协议来拉取数据import { createTRPCProxyClient } from trpc/client; import { createServerSideHelpers } from trpc/react-query/server; import superjson from superjson; const proxyClient createTRPCProxyClientAppRouter({ links: [ httpBatchLink({ url: http://localhost:3000/api/trpc, }), ], transformer: superjson, }); const helpers createServerSideHelpers({ client: proxyClient, });注意此分支不需要ctx和router。对应源码在 ssgProxy.ts若传入的不是TRPCUntypedClient则通过getUntypedClient(client)取出底层 untyped client随后把每次查询转发为一次untypedClient.query(path, input)调用——这条路径与浏览器中客户端发起请求走的是同一条链路。值得一提的是接收参数类型上这两种方式是互斥的联合类型CreateSSGHelpersInternal需要routerctx与CreateSSGHelpersExternal只需要client定义见 ssgProxy.ts类型系统会阻止你同时混用或漏传字段。Helpers 的用法与语义创建出的helpers对象上每个 procedure 都暴露了以下方法对 query 类型的 procedure 而言。prefetch 与 fetch怎么选prefetch与fetch之间最主要的区别在于返回语义fetch就像一个普通函数调用会返回查询结果你需要结果用于服务端逻辑时使用它例如在渲染前计算某个派生值。prefetch不返回结果并且永远不会抛出异常。它只负责把查询写入缓存之后再通过dehydrate()脱水并发送给客户端。如果你需要知道错误请改用fetch。一个实用的经验法则是确定客户端还需要这条数据就用prefetch只是想用服务端拿到的结果就用fetch。与之对应的还有一对prefetchInfinite用于无限列表查询infinite query的缓存预取fetchInfinite则在返回结果的同时完成预取。对于无限查询源码中会将initialCursor或null作为 react-query 的initialPageParam传入见 ssgProxy.ts。底层都是 react-query文档明确指出这些函数本质上是react-query 函数的薄封装Helpers 方法底层 react-query API见 ssgProxy.tsfetchqueryClient.fetchQuery({ ...args, queryKey, queryFn })fetchInfinitequeryClient.fetchInfiniteQuery({ ..., initialPageParam })prefetchqueryClient.prefetchQuery({ ...args, queryKey, queryFn })prefetchInfinitequeryClient.prefetchInfiniteQuery({ ..., initialPageParam })queryOptions/infiniteQueryOptions返回{ ...args, queryKey, queryFn }组合对象每个 helper 都会根据helpers.path.util中的路径自动拼出queryKey经由getQueryKeyInternal因此只要你保持 procedure 调用路径一致客户端useQuery与服务器端 prefetch 就会命中同一个 cache key这是水合能够生效的前提。更完整的QueryOptions进阶用法可查阅 react-query 官方文档其中针对 v4 的指南入口见原文档此处不再赘述。dehydrate用 trpcState 水合客户端所有预取完成后需要把 Query Cache 脱水并通过 Next.js 的props传递给客户端。文档特别强调返回的 props 中必须使用trpcState作为键名客户端的withTRPC才会识别并水合它return { props: { // 非常重要 - 使用 trpcState 作为键 trpcState: helpers.dehydrate(), }, };dehydrate()在内部委托给 react-query 的dehydrate并做了一层默认策略未完成的pending查询会被过滤掉错误查询也会被序列化这样客户端的错误态同样能水合随后去掉缓存条目上不可序列化的promise字段并用你配置的 transformer如 superjson对整个 dehydrated state 执行serialize见 ssgProxy.ts。dehydrate也接受 react-query 标准的DehydrateOptions作为参数以便定制策略。关于trpcState的消费端在 packages/next/src/withTRPC.tsx 中tRPC 会从pageProps.trpcState取出脱水数据、用 transformer 的input.deserialize反序列化再通过HydrationBoundary state{hydratedState}包裹应用——这正是「服务端预取 → 客户端零请求首屏」这一整套机制落地的最后一环。完整示例Pages Router 中的 getServerSideProps以一篇博客文章详情页为例页面路径为pages/posts/[id].tsximport { createServerSideHelpers } from trpc/react-query/server; import { GetServerSidePropsContext, InferGetServerSidePropsType } from next; import { appRouter } from server/routers/_app; import superjson from superjson; import { trpc } from utils/trpc; export async function getServerSideProps( context: GetServerSidePropsContext{ id: string }, ) { const helpers createServerSideHelpers({ router: appRouter, ctx: {}, transformer: superjson, }); const id context.params?.id as string; /* * 预取 post.byId 查询。 * prefetch 不返回结果也从不抛错——如果需要该行为请改用 fetch。 */ await helpers.post.byId.prefetch({ id }); // 确保返回 { props: { trpcState: helpers.dehydrate() } } return { props: { trpcState: helpers.dehydrate(), id, }, }; } export default function PostViewPage( props: InferGetServerSidePropsTypetypeof getServerSideProps, ) { const { id } props; const postQuery trpc.post.byId.useQuery({ id }); if (postQuery.status ! success) { // 因为查询已被预取这里不会发生 return Loading.../; } const { data } postQuery; return ( h1{data.title}/h1 emCreated {data.createdAt.toLocaleDateString()}/em p{data.text}/p h2Raw data:/h2 pre{JSON.stringify(data, null, 4)}/pre / ); }这段代码的运行逻辑是getServerSideProps在服务端用 Helpers 直接执行 procedure 得到数据并写入 cache → 脱水为trpcState→ 组件渲染时trpc.post.byId.useQuery({ id })以水合数据为初始值因此status已经是successLoading...分支永远不会执行。拓展SSG 场景下的 getStaticPropsServer-Side Helpers 最常见的应用场景其实是SSG。静态站点生成需要在每个页面的getStaticProps中执行 tRPC 查询流程与 SSR 几乎一致详见 version-10.x 的 SSG 文档export async function getStaticProps( context: GetStaticPropsContext{ id: string }, ) { const helpers createServerSideHelpers({ router: appRouter, ctx: {}, transformer: superjson, // 可选 - 增加 superjson 序列化 }); const id context.params?.id as string; // 预取 post.byId await helpers.post.byId.prefetch({ id }); return { props: { trpcState: helpers.dehydrate(), id, }, revalidate: 1, }; } export const getStaticPaths: GetStaticPaths async () { // ... 通过 prisma 等数据源查出全部文章 id生成 paths return { paths: posts.map((post) ({ params: { id: post.id } })), fallback: blocking, }; };别忘了关闭客户端重取react-query 的默认行为是客户端挂载时会重新拉取数据即使服务端已经提供了初始数据。如果你的意图是「数据只在getStaticProps阶段获取一次」例如为了减少第三方限流 API 的请求量需要把refetchOnMount和refetchOnWindowFocus设为false。可以按单个查询关闭const data trpc.example.useQuery( // 如果查询无输入注意不要误把 options 当成第一个参数 undefined, { refetchOnMount: false, refetchOnWindowFocus: false }, );也可以在utils/trpc.ts中通过queryClientConfig.defaultOptions.queries全局关闭export const trpc createTRPCNextAppRouter({ config() { return { transformer: superjson, links: [httpBatchLink({ url: ${getBaseUrl()}/api/trpc })], queryClientConfig: { defaultOptions: { queries: { refetchOnMount: false, refetchOnWindowFocus: false, }, }, }, }; }, });⚠️ 注意如果你的应用同时包含静态和动态查询全局关闭重取要格外谨慎动态数据页面可能因此无法刷新。什么时候该用 ssr: true什么时候用 Helpers两者关系可以这样理解开启ssr: true后tRPC 通过getInitialProps在服务端自动预取所有查询但它与手动使用getServerSideProps的方式存在兼容性问题这也是官方在 SSR 文档 FAQ 中明确记录的已知问题。因此官方推荐想要「开箱即用」的 SSR且页面不依赖getServerSideProps可开启ssr: true甚至是按请求条件触发的ssr(opts) boolean回调形式需要精确控制预取范围、或结合getStaticProps/getServerSideProps精细取数时保持默认关闭ssr改用 Server-Side Helpers 手动预取。两种方案最终都以trpcState的形式把 dehydrated cache 交给客户端的 withTRPC.tsx 完成水合区别只在于数据是什么时候、以什么方式被填进缓存的。源码路径速查createServerSideHelpers的完整实现与类型定义packages/react-query/src/server/ssgProxy.ts导出入口trpc/react-query/serverpackages/react-query/src/server/index.ts客户端水合trpcState与HydrationBoundary装配packages/next/src/withTRPC.tsx本系列配套文档v10 Server-Side Helpers 原文、SSG、SSR当前版本v11的等价页面见 www/docs/client/nextjs/pages-router/server-side-helpers.md把握住prefetch/fetch的选择法则与trpcState这一约定键名Server-Side Helpers 就能在 SSG 与 SSR 两种模式下稳定地为你省掉客户端的首屏请求让你的 tRPC 页面「Move Fast and Break Nothing」。【免费下载链接】trpc‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表