news 2026/9/6 20:55:14

tRPC Next.js App Directory 实验适配器:RSC、Server Actions 与缓存的一体化实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
tRPC Next.js App Directory 实验适配器:RSC、Server Actions 与缓存的一体化实战指南

tRPC Next.js App Directory 实验适配器:RSC、Server Actions 与缓存的一体化实战指南

【免费下载链接】trpc🧙‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc

本篇围绕 tRPC 仓库中的实验性示例 examples/.experimental/next-app-dir,讲解如何在 Next.js App Directory 中以"同一套 API 调用方式"同时支持 Server Components(RSC)、Server Actions 与客户端组件,并理解其基于react-server模块条件拆分入口、nextCacheLink缓存与revalidate失效机制的实现细节。读完本文,你可以复现该实验架构的完整搭建步骤,并判断它在生产环境中的适用边界。

一、这是什么:一个官方适配器的 Playground

该示例的定位在 README 中开宗明义:这是一个用于验证"tRPC + Next.js App directory 官方适配器"的 playground 仓库,并明确标注 "This is experimental and is subject to change"(实验性质、接口随时可能变化)。

README 中同时给出了一条重要背景说明:在正式适配器落地之前,App Directory 下已有两条可用的既有路径:

  • 直接在组件中使用@trpc/client(RSC 与非 RSC 组件均可);
  • 在客户端组件中使用@trpc/next

而本实验要验证的是第三条路:一个针对 App Directory 专门设计的客户端/服务端调用层,让开发者"no matter if you are in a server component"(无论身处服务端组件还是客户端组件)都能以相同方式使用 tRPC。

README 中的进度清单如实反映了当前成熟度:

事项状态
RSC 支持的概念验证(PoC)已完成
Server Actions 的概念验证(PoC)已完成
缓存(caching)实现已完成
服务端调用触发的缓存失效未完成
客户端调用触发的缓存失效未完成
服务端与客户端调用的双向失效联动未完成
API 最终定型、重测试未完成

对应地,README 也给出了明确的生产环境警告:"Don't use this in production unless you are okay with large refactoring."——除非你能接受大规模重构成本,否则不要在生产中使用。这个警告在评估任何实验性方案时都应当放在第一位。

二、总体架构:一个本地 tRPC 包,两种运行时入口

实验的核心思路(README 的 Overview 章节)可以概括为两句话:

  1. 创建一个tRPC API handler(路由处理器);
  2. 创建一个本地 tRPC 包,为"use client""use server"场景提供不同的 entrypoint

第二个步骤是整个架构的枢纽:Next.js 打包时会根据模块是否带'use client'标识选择不同的模块条件(module condition)。该示例通过一个通过 pnpm 链接进应用包的本地包trpc-api实现了这种分流。

2.1 用 exports 条件区分 RSC 与客户端入口

包声明见 src/trpc/package.json:

{ "name": "trpc-api", "typings": "client.ts", "exports": { ".": { "types": "./client.ts", "react-server": "./server-invoker.ts", "default": "./client.ts" } } }

关键点:

  • react-server条件命中时(即无'use client'的服务端组件 / Server Components 环境),解析到 server-invoker.ts;
  • 其余情况(客户端组件)走default分支,解析到 client.ts。

应用侧则在 package.json 中通过"trpc-api": "link:./src/trpc"将该本地包以 workspace link 方式引入,与next@^15.3.8react@^19.1.0@tanstack/react-query@^5.80.3等依赖共同工作。这样,同一个import { api } from 'trpc-api'语句,在 RSC 与客户端组件中会拿到两个不同的客户端实现,但暴露相同的调用形态——这正是 README 所承诺的"same way"。

2.2 API Handler:标准 fetch 适配器

README 的第一步"Create an API handler for tRPC"对应 src/app/api/trpc/[trpc]/route.ts:

import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { createContext } from '~/server/context'; import { appRouter } from '~/server/routers/_app'; const handler = (req: Request) => fetchRequestHandler({ endpoint: '/api/trpc', req, router: appRouter, createContext, }); export { handler as GET, handler as POST };

这是标准的 App Directory Route Handler 写法:将fetchRequestHandler同时挂到GET/POST上。源码注释中还留了一行// Add back once NextAuth v5 is releasedruntime = 'edge'占位,说明边缘运行时支持当时受 NextAuth v5 状态限制,这也是一处值得注意的适用前提。

2.3 RSC 入口:nextCacheLink 直接调用,不走 HTTP

server-invoker.ts 是react-server条件下被解析的客户端:

export const api = experimental_createTRPCNextAppDirServer<typeof appRouter>({ config() { return { links: [ loggerLink({ enabled: (op) => true }), experimental_nextCacheLink({ // requests are cached for 5 seconds revalidate: 5, router: appRouter, transformer, createContext: async () => ({ session: await auth(), headers: { cookie: (await cookies()).toString(), 'x-trpc-source': 'rsc-invoke', }, }), }), ], }; }, });

从源码结构看,该 link 有两个核心特征:

  • 进程内直调:文件头注释写明 "This client invokes procedures directly on the server without fetching over HTTP",即传入router后在 Node 进程中直接执行 procedure,省去 HTTP 往返;
  • Next.js 数据缓存接入revalidate: 5将结果按 Next.js 的revalidate语义缓存 5 秒,并复用请求级 context(session、cookie 头),同时通过x-trpc-source: rsc-invoke标记请求来源,便于服务端区分 RSC 直调与浏览器请求。

2.4 客户端入口:HTTP link + Server Action 双通道

client.ts 面向带'use client'的组件,暴露了两个对象:

export const api = experimental_createTRPCNextAppDirClient<AppRouter>({ config() { return { links: [ loggerLink({ enabled: (op) => true }), experimental_nextHttpLink({ transformer, batch: true, url: getUrl(), headers() { return { 'x-trpc-source': 'client' }; }, }), ], }; }, }); export const useAction = experimental_createActionHook<AppRouter>({ links: [loggerLink(), experimental_serverActionLink({ transformer })], });
  • api通过experimental_nextHttpLinkbatch: true)走/api/trpc端点,即 2.2 节的路由处理器;
  • useAction是一个 React Hook,底层由experimental_serverActionLink承载,把 mutation 调用转换为 React Server Action 的表单提交语义。

两个入口共享同一份 shared.ts:getUrl()按环境返回端点地址(浏览器下为相对路径/api/trpc,服务端下回落到http://localhost:3000/api/trpc),transformer则是在superjson基础上注册了Temporal.PlainDate/Temporal.PlainDateTime自定义序列化器(依赖@js-temporal/polyfill),保证 Temporal 类型在跨边界传输后保真。

README 中引用的对照示例即 ClientGreeting.tsx(客户端侧)与 ServerInvokedGreeting.tsx(RSC 侧)。后者展示了 RSC 直调与失效按钮的完整形态:

export async function ServerInvokedGreeting() { const greeting1 = await api.greeting.query({ text: 'i never hit an api endpoint' }); const secret = await api.secret.query(); // ... <form action={async () => { 'use server'; await api.greeting.revalidate({ text: 'i never hit an api endpoint' }); }} > <button type="submit">Revalidate Cache 1</button> </form> }

注意api.secret.query()的用法:直调发生在服务端进程内,可以访问仅服务端可达的数据(如会话密钥),这在经由 HTTP 的客户端调用中无法直接做到——从源码结构看,这是选择 RSC 直调而非一律走 API 端点的主要动机之一。

三、缓存与失效:revalidate procedure + 专用端点

缓存是 README 进度表中唯一"已完成"的能力项("Implement caching")。其落地由三部分组成:

  1. nextCacheLinkrevalidate参数(见 2.3 节),决定 RSC 查询结果在 Next.js data cache 中的存活时间;
  2. router 中声明revalidateprocedure:如上例api.greeting.revalidate(input),按 procedure 输入精确失效对应缓存项,调用入口是一个内联 Server Action('use server');
  3. HTTP 失效端点:src/app/api/trpc/revalidate/route.ts 只有一行——
export { experimental_revalidateEndpoint as POST } from '@trpc/next/app-dir/server';

它把@trpc/next/app-dir/server导出的experimental_revalidateEndpoint直接挂载为POST处理器,供客户端(或浏览器)在不走完整 RPC 流程的情况下触发缓存失效。

需要强调边界:README 进度表中 "Implement cache invalidation on server calls / on client calls" 均为未勾选状态,意味着失效链路当前依赖显式的revalidate调用,尚不具备"一次 mutation 自动联动失效查询缓存"的能力。若你的业务强依赖自动失效,应把这一条列入风险清单。

四、Server Actions:createAction 把 procedure 变成 action

server-action 示例目录 给出了"procedure → Server Action"的标准包装方式:

'use server'; import { createAction, publicProcedure } from '~/server/trpc'; import { z } from 'zod'; export const testAction = createAction( publicProcedure .input(z.object({ text: z.string().min(1) })) .mutation(async (opts) => { console.log('testMutation called', opts); return { text: 'Hello world', date: new Date() }; }), );

createAction复用 tRPC 的 procedure builder(输入校验、middleware 均可保留),仅在语义上把 mutation 标记为 Server Action 友好;客户端侧则通过 2.4 节的useActionHook 消费,目录中的ReactHookFormExample.action.tsx等文件进一步演示了与 React Hook Form 的表单集成(配合@hookform/resolvers与 zod)。

五、配套:TanStack Query 的 RSC 预取与水合

playground 中还内置了 RSC 预取链路,可直接作为"App Directory + TanStack Query + tRPC"的组合参考:

  • 服务端 rq-server.tsx:createTRPCOptionsProxy生成trpc选项代理(内部经server-only包锁定在服务端),并导出HydrateClient(基于dehydrate+HydrationBoundary)与prefetch(自动区分prefetchQuery/prefetchInfiniteQuery)。context 与 queryClient 均用cache()包裹,保证同一请求内稳定复用;
  • 客户端 rq-client.tsx:getQueryClient采用"服务端每次新建、浏览器单例"的经典模式;
  • 序列化配置在 shared.ts:staleTime设为 30 秒以避免水合后立即重复拉取,且shouldDehydrateQuery放开了pending状态查询的脱水——源码注释解释了原因:"we set a stale time so that queries aren't immediately refetched on the client" 以及 "include pending queries in dehydration ... allows us to prefetch in RSC and send promises over the RSC boundary",即允许把进行中的 Promise 经由 RSC 边界传给客户端续用;
  • 消费端示例见 rsc-rq-prefetch/post.tsx:useSuspenseQuery(trpc.getLatestPost.queryOptions())搭配useMutation+invalidateQueries完成"读预取、写后手动失效"的闭环。

六、验证方式与工程约束

  • 包脚本(package.json)提供dev/build/start,以及基于 Playwright 的 e2e:test:e2etest-devstart-server-and-test起 3000 端口后跑playwright test);
  • 测试文件 test/client.test.ts 与 test/server-cache.test.ts 分别覆盖客户端行为与服务端缓存语义,是理解nextCacheLink行为契约的入口;
  • 目录名.experimental(带前导点)本身即仓库层面的"隔离"声明:README 提示若要贡献修改,应到上游仓库的对应目录进行,并通过官方社区渠道讨论方案取向(README 指向官方 Discord)。

七、适用判断:什么时候值得参考这个方案

综合 README 与源码,该实验方案的取舍可以归纳为:

  • 收益:RSC 与客户端共享同一 router 类型与调用语法;RSC 直调省 HTTP 往返并可访问服务端私有数据;revalidate机制让 RSC 数据缓存可控;procedure 体系(输入校验、中间件、transformer)完整保留。
  • 代价与限制:全部 API 均带experimental_前缀(experimental_createTRPCNextAppDirClient/experimental_nextHttpLink/experimental_createTRPCNextAppDirServer/experimental_nextCacheLink/experimental_serverActionLink/experimental_createActionHook/experimental_revalidateEndpoint),接口稳定性不保证;缓存自动失效链路未完成;README 明确警告生产使用可能伴随大规模重构。
  • 建议姿势:把它当作"App Directory 下 tRPC 架构演进的活参考",重点研读react-server入口拆分、nextCacheLink与 revalidate 端点的设计;若需在生产落地,当前更稳妥的组合仍是 README 提示的既有路径——组件内直接使用@trpc/client,或在客户端组件中沿用@trpc/next,并配合 next-minimal-starter 等稳定示例。

理解这一实验仓库,等于拿到了一张"tRPC 官方 App Directory 适配器"的需求清单与原型实现:入口按模块条件分流、RSC 进程内直调、缓存与失效显式化、Server Action 包装 procedure——这四点很可能就是正式适配器 API 定型时的骨架。

【免费下载链接】trpc🧙‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 20:53:30

BIQS 2.0进阶:防错验证、快速响应与分层审核的落地要点

简介&#xff1a;在制造业质量管理中&#xff0c;过程控制的有效性往往决定客户审核的结果。许多工厂虽然建立了防错点检和快速响应机制&#xff0c;却因验证失效、闭环不足而被开出Major不符合项。质量内建的核心并非增加表格&#xff0c;而是确保每个动作能真正拦截缺陷、驱动…

作者头像 李华
网站建设 2026/9/6 20:52:57

智能制药解决方案PPT制作全攻略:内容设计、版式规范与文件修复

简介&#xff1a;智能制药解决方案演示文稿是一份面向制药企业管理者、生产信息化从业者及智能制造咨询顾问的专业培训与方案设计材料。内容以制药管理难点为起点&#xff0c;分别阐述物料追踪、书面文档滞后、储存条件监管、手动操作偏差等痛点&#xff0c;再系统展示智慧制药…

作者头像 李华
网站建设 2026/9/6 20:52:25

异步流整形ATS:TSN中不依赖时间同步的流量平滑机制

简介&#xff1a;IEEE 802.1Qcr-2020是TSN&#xff08;时间敏感网络&#xff09;协议族的重要标准文件&#xff0c;面向工业自动化、汽车、航空航天及电信领域网络工程师&#xff0c;解决传统以太网在实时性和确定性传输上的不足。这一修正案基于IEEE 802.1Q-2018&#xff0c;整…

作者头像 李华
网站建设 2026/9/6 20:47:52

基于STM32与MLX90614的红外测温报警系统设计与实现

简介&#xff1a;基于STM32的红外测温系统设计文档&#xff0c;面向嵌入式系统学习者、电子竞赛参赛者和相关专业毕业生&#xff0c;提供从课题背景、技术现状到方案论证、软硬件实现的完整设计资料&#xff0c;适用于医疗、工业等非接触测温场景。文档以STM32F103微控制器为处…

作者头像 李华
网站建设 2026/9/6 20:44:37

Qwerty Learner 常见问题速查:从安装报错到数据异常的完整排障指南

Qwerty Learner 常见问题速查&#xff1a;从安装报错到数据异常的完整排障指南 【免费下载链接】qwerty-learner 为键盘工作者设计的单词记忆与英语肌肉记忆锻炼软件 / Words learning and English muscle memory training software designed for keyboard workers 项目地址:…

作者头像 李华