Introduction
tRPC allows you to create TypeScript APIs without writing OpenAPI definitions or manual contracts. The typing is automatically shared between the client and the server. This approach eliminates runtime errors related to data and speeds up development. In 2026, tRPC remains the preferred choice for Next.js projects seeking simplicity and maximum type safety.
Prerequisites
- Node.js 20 or higher
- Next.js 15 with App Router
- Basic knowledge of TypeScript
- npm or pnpm installed
Initialize the Next.js Project
npx create-next-app@latest my-trpc-app --yes
cd my-trpc-app
npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zodThis command creates a fresh Next.js project and installs the essential tRPC packages along with Zod for validation.
Create the Main tRPC Router
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
const t = initTRPC.create({
transformer: superjson,
});
export const router = t.router;
export const publicProcedure = t.procedure;We initialize tRPC with SuperJSON to support complex types like Date. The router and procedure exports will be reused throughout the project.
Define the API Routes
import { router, publicProcedure } from '../trpc';
import { z } from 'zod';
export const userRouter = router({
getById: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return { id: input.id, name: 'Alice', email: 'alice@example.com' };
}),
create: publicProcedure
.input(z.object({ name: z.string().min(2) }))
.mutation(async ({ input }) => {
return { id: 'new-id', ...input };
}),
});Each procedure is typed thanks to Zod. getById is a query and create is a mutation. Typing is automatic on the client side.
Assemble the Root Router
import { router } from '../trpc';
import { userRouter } from './user';
export const appRouter = router({
user: userRouter,
});
export type AppRouter = typeof appRouter;The main router combines all sub-routers. The AppRouter type is exported for the client.
Create the Next.js API Handler
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
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 };This file exposes all tRPC procedures via Next.js App Router. No additional configuration is required.
Configure the React Provider
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import { createTRPCReact } from '@trpc/react-query';
import { AppRouter } from '@/server/routers/_app';
import { useState } from 'react';
export const trpc = createTRPCReact<AppRouter>();
export function TRPCProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [httpBatchLink({ url: '/api/trpc' })],
})
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</trpc.Provider>
);
}The provider connects React Query and tRPC. Place it in the root layout so all components have access to it.
Best Practices
- Always use Zod to validate inputs
- Separate routers by business domain
- Enable SuperJSON from the start to support Dates and Maps
- Name your procedures clearly (get, create, update, delete)
- Write integration tests for critical procedures
Common Mistakes to Avoid
- Forgetting to export the AppRouter type on the server
- Not placing the TRPCProvider in the root layout
- Using procedures without input when data is expected
- Ignoring Zod validation errors that don't surface properly on the client
Go Further
Discover our complete tRPC and Next.js courses at Learni Group.