tRPCSeniorSystem design
Как использовать tRPC с Next.js (App Router и Pages Router)?
В Pages Router используется createNextApiHandler для API-route и createTRPCNext для React-хуков. В App Router серверные компоненты вызывают процедуры напрямую через createCallerFactory, клиентские компоненты — через createTRPCReact с fetchRequestHandler.
tRPC в Next.js Pages Router
Классический подход: API Route в pages/api/trpc/[trpc].ts обрабатывает все запросы через fetchRequestHandler или createNextApiHandler.
// pages/api/trpc/[trpc].ts
import { createNextApiHandler } from '@trpc/server/adapters/next';
import { appRouter } from '../../../server/trpc/router';
import { createContext } from '../../../server/trpc/context';
export default createNextApiHandler({
router: appRouter,
createContext,
onError({ error, path }) {
if (error.code === 'INTERNAL_SERVER_ERROR') {
console.error(`Error on ${path}:`, error);
}
},
});
// utils/trpc.ts (Pages Router клиент)
import { createTRPCNext } from '@trpc/next';
import { httpBatchLink } from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from '../server/trpc/router';
export const trpc = createTRPCNext<AppRouter>({
config() {
return {
links: [
httpBatchLink({
url: '/api/trpc',
transformer: superjson,
}),
],
};
},
ssr: false, // или true для SSR с prefetch
});
// pages/_app.tsx
import { trpc } from '../utils/trpc';
export default trpc.withTRPC(MyApp);
tRPC в Next.js App Router
App Router различает серверные и клиентские компоненты. Подход разный для каждого случая.
API Route Handler (app/api/trpc/[trpc]/route.ts)
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '../../../../server/trpc/router';
import { createContext } from '../../../../server/trpc/context';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => createContext(req),
});
export { handler as GET, handler as POST };
Серверные компоненты — прямые вызовы без HTTP
// app/posts/page.tsx (Server Component)
import { createCallerFactory } from '@trpc/server';
import { appRouter } from '../../server/trpc/router';
import { createContext } from '../../server/trpc/context';
import { headers } from 'next/headers';
const createCaller = createCallerFactory(appRouter);
export default async function PostsPage() {
// Прямой вызов без HTTP-запроса
const ctx = await createContext(new Request('http://internal', {
headers: await headers(),
}));
const caller = createCaller(ctx);
const posts = await caller.post.list({ limit: 10 });
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Клиентские компоненты в App Router
// lib/trpc/client.ts
'use client';
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../../server/trpc/router';
export const trpc = createTRPCReact<AppRouter>();
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import superjson from 'superjson';
import { useState } from 'react';
import { trpc } from '../lib/trpc/client';
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: '/api/trpc',
transformer: superjson,
}),
],
})
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</trpc.Provider>
);
}
Подводные камни
- В App Router нельзя использовать
createTRPCNext(он для Pages Router) — это частая ошибка при миграции. - Файл с
createTRPCReactдолжен иметь директиву'use client'вверху, иначе Next.js выбросит ошибку о использовании клиентских API в Server Component. - Серверные компоненты не должны импортировать клиентский trpc-файл — создавайте отдельные файлы для серверного (
createCallerFactory) и клиентского (createTRPCReact) кода. - При SSR в Pages Router с
ssr: trueданные сериализуются через dehydrate/hydrate — при использовании superjson нужно настроить трансформер и для этого слоя. - Route Handler в App Router (
route.ts) должен экспортировать и GET, и POST — tRPC использует GET для queries и POST для mutations/batch. createCallerFactoryв Server Components не проходит через HTTP-middleware (CORS, rate limiting) — авторизацию нужно проверять в контексте явно.- Providers-компонент с QueryClient должен создавать новый экземпляр через
useState, а не на уровне модуля — иначе данные будут шариться между запросами на сервере.
Common mistakes
- Смешивать «tRPC с Next.js» с похожим механизмом без критерия выбора.
- Игнорировать риск: неверно оценить границы применения темы «tRPC с Next.js» и получить хрупкое решение.
- Показывать только синтаксис и не объяснять поведение в runtime или сборке.
What the interviewer is testing
- Объясняет интеграция с App Router, Pages Router, SSR и server components.
- Показывает на примере, как работает: в Pages Router используют next adapter и SSR helpers, а в App Router важны route handlers, server/client component boundary и создание QueryClient без утечки между requests.
- Называет production-нюанс и граничный случай для темы «tRPC с Next.js».