핵심 요약
tRPC 12가 RSC(Server Components)와 정식 통합. 같은 procedure를 client useQuery·Server Action·RSC 직접 호출 3가지 방식으로 호출 가능. 사내 18 procedure 마이그레이션 후 코드 28% 감소, 타입 안정성 100%.
1. RSC 직접 호출 — caller pattern
// server/trpc.ts
import { createCallerFactory } from '@trpc/server'
import { appRouter } from './routers/_app'
export const createCaller = createCallerFactory(appRouter)
// app/posts/page.tsx (RSC)
import { createCaller } from '@/server/trpc'
import { cookies } from 'next/headers'
export default async function Page() {
const caller = createCaller({ session: await getSession(cookies()) })
const posts = await caller.post.list({ limit: 20 })
return
}
HTTP 왕복 없이 RSC에서 procedure 직접 호출 — request waterfall 1단계 단축.
2. Server Action 패턴
// app/actions.ts
'use server'
import { createCaller } from '@/server/trpc'
export async function createPostAction(formData: FormData) {
const caller = createCaller(await getCtx())
return caller.post.create({
title: formData.get('title') as string,
body: formData.get('body') as string,
})
}
form action 그대로, procedure 한 번 정의로 client·server·RSC 모두 사용.
3. Streaming subscriptions — SSE
12.0에서 subscriptions가 SSE 표준. WebSocket 의존 제거. Edge runtime 호환.
4. 마이그레이션 효과
| 지표 | Before(REST+tRPC 11) | After(tRPC 12) |
|---|---|---|
| 코드 줄수 | 18,400 | 13,250 |
| 타입 에러(런타임) | 월 ~12건 | 0 |
| p99 응답(서버) | 180ms | 82ms |
5. 함정
- createCaller 컨텍스트 누락 — RSC에서 ctx 빠뜨리면 권한 검사 통과, 보안 사고. middleware 명시
- Server Action revalidation — caller.x.create() 후 revalidatePath 수동 호출 필요
- Suspense boundary — caller가 throw하면 nearest error boundary로, fallback 설계
- Client component에서 caller 호출 금지 — 빌드 통과하지만 런타임 에러

댓글 0