Tools: Stop manually setting up tRPC in Next.js — use this CLI instead

Tools: Stop manually setting up tRPC in Next.js — use this CLI instead

Source: Dev.to

The Problem ## The Solution ## What gets detected automatically: ## What gets generated: ## layout.tsx — auto-patched: ## Using tRPC After Setup ## Why Not create-t3-app? ## protectedProcedure — Already Included ## Try It Every time I start a new Next.js project with tRPC, I do the same thing. Open docs. Copy files. Install packages. Forget to wrap layout.tsx. Get the QueryClient error. Fix it. Repeat next project. I got tired of it. So I built a CLI. Setting up tRPC v11 with Next.js App Router is not hard — but it's tedious. You need: Miss any step → error. Every new project. Run this inside any existing Next.js project. Everything happens automatically. Server Component — prefetch data: Client Component — use data: create-t3-app is great — but it only works for new projects. create-trpc-setup works with existing projects. Already have a Next.js app with Clerk, Shadcn, and custom providers? No problem. Run the command, everything gets added without touching your existing code. The generated init.ts includes a protectedProcedure that automatically throws UNAUTHORIZED: Use it in any router: If it saved you time, drop a ⭐ on GitHub and share it with your team. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: npx create-trpc-setup Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: npx create-trpc-setup CODE_BLOCK: npx create-trpc-setup CODE_BLOCK: npx create-trpc-setup Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: npx create-trpc-setup CODE_BLOCK: npx create-trpc-setup CODE_BLOCK: trpc/ ├── init.ts ← context, baseProcedure, protectedProcedure, Zod error formatter ├── query-client.ts ← SSR-safe QueryClient ├── client.tsx ← TRPCReactProvider + useTRPC hook ├── server.tsx ← prefetch, HydrateClient, caller └── routers/ └── _app.ts ← health + greet procedures with Zod app/api/trpc/[trpc]/route.ts ← API handler with real headers app/trpc-status/ ← test page (delete after confirming) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: trpc/ ├── init.ts ← context, baseProcedure, protectedProcedure, Zod error formatter ├── query-client.ts ← SSR-safe QueryClient ├── client.tsx ← TRPCReactProvider + useTRPC hook ├── server.tsx ← prefetch, HydrateClient, caller └── routers/ └── _app.ts ← health + greet procedures with Zod app/api/trpc/[trpc]/route.ts ← API handler with real headers app/trpc-status/ ← test page (delete after confirming) CODE_BLOCK: trpc/ ├── init.ts ← context, baseProcedure, protectedProcedure, Zod error formatter ├── query-client.ts ← SSR-safe QueryClient ├── client.tsx ← TRPCReactProvider + useTRPC hook ├── server.tsx ← prefetch, HydrateClient, caller └── routers/ └── _app.ts ← health + greet procedures with Zod app/api/trpc/[trpc]/route.ts ← API handler with real headers app/trpc-status/ ← test page (delete after confirming) CODE_BLOCK: <body> {children} <Toaster /> </body> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: <body> {children} <Toaster /> </body> CODE_BLOCK: <body> {children} <Toaster /> </body> CODE_BLOCK: <body> <TRPCReactProvider> {children} <Toaster /> </TRPCReactProvider> </body> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: <body> <TRPCReactProvider> {children} <Toaster /> </TRPCReactProvider> </body> CODE_BLOCK: <body> <TRPCReactProvider> {children} <Toaster /> </TRPCReactProvider> </body> CODE_BLOCK: // app/page.tsx import { HydrateClient, prefetch, trpc } from "@/trpc/server"; import { MyClient } from "./my-client"; export default function Page() { prefetch(trpc.greet.queryOptions({ name: "World" })); return ( <HydrateClient> <MyClient /> </HydrateClient> ); } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // app/page.tsx import { HydrateClient, prefetch, trpc } from "@/trpc/server"; import { MyClient } from "./my-client"; export default function Page() { prefetch(trpc.greet.queryOptions({ name: "World" })); return ( <HydrateClient> <MyClient /> </HydrateClient> ); } CODE_BLOCK: // app/page.tsx import { HydrateClient, prefetch, trpc } from "@/trpc/server"; import { MyClient } from "./my-client"; export default function Page() { prefetch(trpc.greet.queryOptions({ name: "World" })); return ( <HydrateClient> <MyClient /> </HydrateClient> ); } CODE_BLOCK: // my-client.tsx "use client"; import { useSuspenseQuery } from "@tanstack/react-query"; import { useTRPC } from "@/trpc/client"; export function MyClient() { const trpc = useTRPC(); const { data } = useSuspenseQuery(trpc.greet.queryOptions({ name: "World" })); return <div>{data.message}</div>; } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // my-client.tsx "use client"; import { useSuspenseQuery } from "@tanstack/react-query"; import { useTRPC } from "@/trpc/client"; export function MyClient() { const trpc = useTRPC(); const { data } = useSuspenseQuery(trpc.greet.queryOptions({ name: "World" })); return <div>{data.message}</div>; } CODE_BLOCK: // my-client.tsx "use client"; import { useSuspenseQuery } from "@tanstack/react-query"; import { useTRPC } from "@/trpc/client"; export function MyClient() { const trpc = useTRPC(); const { data } = useSuspenseQuery(trpc.greet.queryOptions({ name: "World" })); return <div>{data.message}</div>; } COMMAND_BLOCK: export const protectedProcedure = t.procedure.use(({ ctx, next }) => { if (!ctx.userId) { throw new TRPCError({ code: "UNAUTHORIZED" }); } return next({ ctx: { ...ctx, userId: ctx.userId } }); }); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: export const protectedProcedure = t.procedure.use(({ ctx, next }) => { if (!ctx.userId) { throw new TRPCError({ code: "UNAUTHORIZED" }); } return next({ ctx: { ...ctx, userId: ctx.userId } }); }); COMMAND_BLOCK: export const protectedProcedure = t.procedure.use(({ ctx, next }) => { if (!ctx.userId) { throw new TRPCError({ code: "UNAUTHORIZED" }); } return next({ ctx: { ...ctx, userId: ctx.userId } }); }); COMMAND_BLOCK: getProfile: protectedProcedure.query(({ ctx }) => { return { userId: ctx.userId }; // guaranteed non-null }), Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: getProfile: protectedProcedure.query(({ ctx }) => { return { userId: ctx.userId }; // guaranteed non-null }), COMMAND_BLOCK: getProfile: protectedProcedure.query(({ ctx }) => { return { userId: ctx.userId }; // guaranteed non-null }), CODE_BLOCK: npx create-trpc-setup Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: npx create-trpc-setup CODE_BLOCK: npx create-trpc-setup - Install @trpc/server, @trpc/client, @trpc/tanstack-react-query, @tanstack/react-query, zod, server-only... - Create trpc/init.ts with context and procedures - Create trpc/query-client.ts with SSR-safe QueryClient - Create trpc/client.tsx with TRPCReactProvider - Create trpc/server.tsx with HydrateClient and prefetch - Create app/api/trpc/[trpc]/route.ts - Update layout.tsx to wrap children with TRPCReactProvider - Package manager — npm, pnpm, yarn, or bun - Path alias — reads tsconfig.json for @/*, ~/*, or any custom alias - Auth provider — detects Clerk or NextAuth and configures context - Folder structure — src/ or root layout - 📦 npm: npmjs.com/package/create-trpc-setup - 🐙 GitHub: github.com/Dhavalkurkutiya/create-trpc-setup