cursor.directory
--- description: Guidelines for writing Next.js apps with tRPC globs: "**/*.ts, **/*.tsx, **/*.js, **/*.jsx" --- ## Overview [tRPC](https://trpc.io/) enables end-to-end typesafe APIs, allowing you to build and consume APIs without schemas, code generation, or runtime errors. These rules will help you follow best practices for tRPC v11. ## Project Structure For a clean tRPC setup, follow this recommended structure: ``` . ├── src │ ├── pages │ │ ├── _app.tsx # add `createTRPCNext` setup here │ │ ├── api │ │ │ └── trpc │ │ │ └── [trpc].ts # tRPC HTTP handler │ │ ├── server │ │ │ ├── routers │ │ │ │ ├── _app.ts # main app router │ │ │ │ ├── [feature].ts # feature-specific routers │ │ │ │ └── [...] │ │ │ ├── context.ts # create app context │ │ │ └── trpc.ts # procedure helpers │ │ └── utils │ │ └── trpc.ts # typesafe tRPC hooks ``` ## Server-Side Setup ### Initialize tRPC Backend ```typescript // server/trpc.ts import { initTRPC } from '@trpc/server'; // Initialize tRPC backend (should be done once per backend) const t = initTRPC.create(); // Export reusable router and procedure helpers export const router = t.router; export const publicProcedure = t.procedure; ``` ### Create Router ```typescript // server/routers/_app.ts import { z } from 'zod'; import { router, publicProcedure } from '../trpc'; export const appRouter = router({ // Your procedures here greeting: publicProcedure .input(z.object({ name: z.string() })) .query(({ input }) => { return `Hello ${input.name}`; }), }); // Export type definition of API (not the router itself!) export type AppRouter = typeof appRouter; ``` ## Client-Side Setup ### Next.js Integration ```typescript // utils/trpc.ts import { httpBatchLink } from '@trpc/client'; import { createTRPCNext } from '@trpc/next'; import type { AppRouter } from '../server/routers/_app'; function getBaseUrl() { if (typeof window !== 'undefined') return ''; // browser should use relative path if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost } export const trpc = createTRPCNext<AppRouter>({ config() { return { links: [ httpBatchLink({ url: `${getBaseUrl()}/api/trpc`, // Include auth headers when needed async headers() { return { // authorization: getAuthCookie(), }; }, }), ], }; }, ssr: false, // Set to true if you want to use server-side rendering }); ``` ## Best Practices 1. **Use Zod for Input Validation**: Always validate procedure inputs with Zod for better type safety and runtime validation. ```typescript import { z } from 'zod'; procedure .input(z.object({ id: z.string().uuid(), email: z.string().email(), age: z.number().min(18) })) .mutation(({ input }) => { /* your code */ }) ``` 2. **Organize Routers by Feature**: Split your routers into logical domains/features rather than having one large router. ```typescript // server/routers/user.ts export const userRouter = router({ list: publicProcedure.query(() => { /* ... */ }), byId: publicProcedure.input(z.string()).query(({ input }) => { /* ... */ }), create: publicProcedure.input(/* ... */).mutation(({ input }) => { /* ... */ }), }); // server/routers/_app.ts import { userRouter } from './user'; import { postRouter } from './post'; export const appRouter = router({ user: userRouter, post: postRouter, }); ``` 3. **Use Middleware for Common Logic**: Apply middleware for authentication, logging, or other cross-cutting concerns. ```typescript const isAuthed = t.middleware(({ next, ctx }) => { if (!ctx.user) { throw new TRPCError({ code: 'UNAUTHORIZED' }); } return next({ ctx: { // Add user information to context user: ctx.user, }, }); }); const protectedProcedure = t.procedure.use(isAuthed); ``` 4. **Use Proper Error Handling**: Utilize tRPC's error handling for consistent error responses. ```typescript import { TRPCError } from '@trpc/server'; publicProcedure .input(z.string()) .query(({ input }) => { const user = getUserById(input); if (!user) { throw new TRPCError({ code: 'NOT_FOUND', message: `User with id ${input} not found`, }); } return user; }); ``` 5. **Use Data Transformers**: Use SuperJSON for automatic handling of dates, Maps, Sets, etc. ```typescript import { initTRPC } from '@trpc/server'; import superjson from 'superjson'; const t = initTRPC.create({ transformer: superjson, }); ``` 6. **Leverage React Query Integration**: For React projects, use tRPC's React Query utilities for data fetching, mutations, and caching. ```tsx function UserProfile({ userId }: { userId: string }) { const { data, isLoading, error } = trpc.user.byId.useQuery(userId); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return <div>{data.name}</div>; } ``` 7. **Context Creation**: Create a proper context object to share resources across procedures. ```typescript // server/context.ts import { inferAsyncReturnType } from '@trpc/server'; import * as trpcNext from '@trpc/server/adapters/next'; import { prisma } from './prisma'; export async function createContext({ req, res, }: trpcNext.CreateNextContextOptions) { const user = await getUser(req); return { req, res, prisma, user, }; } export type Context = inferAsyncReturnType<typeof createContext>; ``` 8. **Type Exports**: Only export types, not the actual router implementations, from your server code to client code. ```typescript // Export type router type signature, NOT the router itself export type AppRouter = typeof appRouter; ``` 9. **Procedure Types**: Use different procedure types for different authorization levels. ```typescript export const publicProcedure = t.procedure; export const protectedProcedure = t.procedure.use(isAuthed); export const adminProcedure = t.procedure.use(isAdmin); ``` 10. **Performance Optimization**: Use batching and prefetching for optimized data loading. ```typescript // Client-side batching in Next.js setup httpBatchLink({ url: `${getBaseUrl()}/api/trpc`, maxURLLength: 2083, }) // Prefetching data in Next.js export async function getStaticProps() { const ssg = createServerSideHelpers({ router: appRouter, ctx: {}, }); await ssg.post.byId.prefetch('1'); return { props: { trpcState: ssg.dehydrate(), }, revalidate: 1, }; } ``` ## Version Compatibility This guide is for tRPC v11, which requires: - TypeScript >= 5.7.2 - Strict TypeScript mode (`"strict": true` in tsconfig.json) ## Further Resources - [Official Documentation](https://trpc.io/docs) - [GitHub Repository](https://github.com/trpc/trpc) - [Example Apps](https://trpc.io/docs/example-apps)

tRPC