6.1 项目目录结构
标准的 Next.js 15 + tRPC 项目结构如下:
my-app/
├── app/
│ ├── api/
│ │ └── trpc/
│ │ └── [trpc]/
│ │ └── route.ts # HTTP 端点
│ ├── layout.tsx
│ └── page.tsx
├── server/
│ ├── trpc.ts # initTRPC,publicProcedure
│ ├── context.ts # createContext
│ ├── router.ts # appRouter + AppRouter 类型
│ └── routers/
│ ├── user.ts
│ └── post.ts
├── lib/
│ ├── trpc.ts # createTRPCReact 客户端
│ └── trpc-server.ts # 服务端 caller(Server Components 用)
└── components/
└── providers.tsx # TRPCProvider + QueryClientProvider
STRUCTURE
6.2 Route Handler 配置
Next.js App Router 使用 Route Handler(route.ts)而非 Pages Router 的 API Routes。tRPC 提供 fetchRequestHandler 适配器,兼容 Edge Runtime。
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/router';
import { createContext } from '@/server/context';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => createContext({ req }),
// 生产环境屏蔽错误详情
onError({ path, error }) {
if (process.env.NODE_ENV === 'development') {
console.error(`tRPC error on '${path}':`, error);
}
},
});
// 同时处理 GET(Query)和 POST(Mutation)
export const { GET, POST } = { GET: handler, POST: handler };
TS
6.3 Server Component 中直接调用
在 Server Component 中,可以绕过 HTTP 层直接调用 Procedure。这利用了 tRPC 的 caller API,性能更高,且可以在服务端使用 Next.js 的 cache() 进行请求去重。
// lib/trpc-server.ts — 服务端 caller(Server Components 专用)
import { cache } from 'react';
import { createCallerFactory } from '@trpc/server';
import { appRouter } from '@/server/router';
import { createContext } from '@/server/context';
import { headers } from 'next/headers';
const createCaller = createCallerFactory(appRouter);
// React cache() 确保同一请求内多次调用只创建一次 Context
export const createServerCaller = cache(async () => {
const heads = await headers();
const ctx = await createContext({
req: new Request('http://localhost', {
headers: heads,
}),
});
return createCaller(ctx);
});
TS
// app/posts/page.tsx — Server Component 直接调用
import { createServerCaller } from '@/lib/trpc-server';
export default async function PostsPage() {
// 直接调用,无 HTTP 开销,支持 Next.js 数据缓存
const caller = await createServerCaller();
const posts = await caller.post.list({ page: 1 });
// posts 类型完全推断
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
TSX
6.4 Hydration:服务端预取 + 客户端接管
为了兼顾 SEO(服务端渲染)和交互性(客户端 Hooks),tRPC 支持在服务端预取数据,然后在客户端 Hydrate。
// app/posts/page.tsx — 服务端预取
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
import { createServerCaller } from '@/lib/trpc-server';
import { getQueryKey } from '@trpc/react-query';
import { trpc } from '@/lib/trpc';
import { PostsClient } from './posts-client';
export default async function PostsPage() {
const queryClient = new QueryClient();
const caller = await createServerCaller();
// 服务端预取,填充 queryClient 缓存
await queryClient.prefetchQuery({
queryKey: getQueryKey(trpc.post.list, { page: 1 }, 'query'),
queryFn: () => caller.post.list({ page: 1 }),
});
return (
{/* 将服务端缓存注入客户端 */}
<HydrationBoundary state={dehydrate(queryClient)}>
<PostsClient />
</HydrationBoundary>
);
}
TSX
6.5 Server Actions vs tRPC 选型
| 维度 | Server Actions | tRPC |
|---|---|---|
| 类型安全 | 基本(需要手动推断) | 完整端到端推断 |
| 缓存 | 不自动缓存 | TanStack Query 全套缓存 |
| 乐观更新 | useOptimistic(手动) | TanStack Query 内置支持 |
| 错误处理 | 需要手动 try/catch | 统一错误类型+onError |
| 请求状态 | useFormStatus(有限) | isPending/isLoading 等完整状态 |
| 复用性 | 仅限 React 客户端 | 可从任意客户端调用 |
| 学习曲线 | 低 | 略高(需要学 tRPC + TanStack Query) |
实践建议简单表单提交、页面级数据变更用 Server Actions(无需额外配置);复杂数据交互、需要乐观更新、多组件共享数据状态的场景用 tRPC。两者可以在同一项目中共存。
6.6 完整脚手架:create-t3-app
create-t3-app 是社区最流行的 Next.js + tRPC + Prisma + Auth.js 集成脚手架,开箱即用:
# 创建 T3 Stack 项目
pnpm create t3-app@latest
# 交互式选项:
# ✔ TypeScript? › Yes
# ✔ tRPC? › Yes
# ✔ Prisma? › Yes
# ✔ NextAuth.js? › Yes
# ✔ Tailwind CSS? › Yes
# ✔ App Router? › Yes
SHELL
# T3 App 的目录结构
src/
├── app/
│ ├── api/
│ │ ├── auth/[...nextauth]/route.ts
│ │ └── trpc/[trpc]/route.ts
│ ├── layout.tsx
│ └── page.tsx
├── server/
│ ├── api/
│ │ ├── root.ts # appRouter
│ │ ├── trpc.ts # initTRPC + procedures
│ │ └── routers/
│ │ └── post.ts
│ ├── auth.ts
│ └── db.ts
└── trpc/
├── query-client.ts
├── react.tsx # 客户端 Provider
└── server.ts # 服务端 caller
STRUCTURE
本章小结Next.js 15 + tRPC 的核心配置:① app/api/trpc/[trpc]/route.ts 使用 fetchRequestHandler 暴露 HTTP 端点;② Server Component 通过 createCallerFactory 直接调用 Procedure(零 HTTP 开销);③ 服务端预取 + HydrationBoundary 实现 SSR + 客户端 Hydration。对于快速启动,推荐使用 create-t3-app 脚手架。