nextjs-pages-router
Set up tRPC in Next.js Pages Router with createNextApiHandler, createTRPCNext, withTRPC HOC, SSR via ssr option and ssrPrepass, SSG via createServerSideHelpers with getStaticProps, and server-side helpers for getServerSideProps prefetching.
- 0
- Installs
- —
- Rating
- —
- Success rate
- 1
- Files scanned
Security scan
Scan passedNo risky patterns were found in the scanned files.
Content sha256 f1aa9eddfb257ff5… — run codexguild_scan_skills after installing to verify your local copy.
Static analysis is a first line of defense, not a guarantee. Read the source
SKILL.md
This skill builds on [server-setup] and [client-setup]. Read them first for foundational concepts.
tRPC -- Next.js Pages Router
File Structure
.
├── src
│ ├── pages
│ │ ├── _app.tsx # withTRPC() HOC
│ │ ├── api/trpc
│ │ │ └── [trpc].ts # tRPC API handler
│ │ └── index.tsx # page using tRPC hooks
│ ├── server
│ │ ├── routers
│ │ │ └── _app.ts # main app router
│ │ ├── context.ts # createContext
│ │ └── trpc.ts # initTRPC, procedure helpers
│ └── utils
│ └── trpc.ts # createTRPCNext, hooks
└── ...
Setup
1. Install
npm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
2. Server init
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const router = t.router;
export const procedure = t.procedure;
3. Define the router
import { z } from 'zod';
import { procedure, router } from '../trpc';
export const appRouter = router({
hello: procedure.input(z.object({ text: z.string() })).query(({ input }) => ({
greeting: `hello ${input.text}`,
})),
});
export type AppRouter = typeof appRouter;
4. API handler
import { createNextApiHandler } from '@trpc/server/adapters/next';
import { appRouter } from '../../../server/routers/_app';
export default createNextApiHandler({
router: appRouter,
createContext: () => ({}),
});
5. Create tRPC hooks
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import type { AppRouter } from '../server/routers/_app';
function getBaseUrl() {
if (typeof window !== 'undefined') return '';
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
return `http://localhost:${process.env.PORT ?? 3000}`;
}
export const trpc = createTRPCNext<AppRouter>({
config() {
return {
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
};
},
ssr: false,
});
6. Wrap app with withTRPC HOC
import type { AppType } from 'next/app';
import { trpc } from '../utils/trpc';
const MyApp: AppType = ({ Component, pageProps }) => {
return <Component {...pageProps} />;
};
export default trpc.withTRPC(MyApp);
7. Use hooks in pages
import { trpc } from '../utils/trpc';
export default function IndexPage() {
const hello = trpc.hello.useQuery({ text: 'client' });
if (!hello.data) return <div>Loading...</div>;
return <p>{hello.data.greeting}</p>;
}
Core Patterns
SSR with ssr: true
Enable SSR to prefetch all queries on the server automatically. Requires ssrPrepass and forwarding client headers.
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import { ssrPrepass } from '@trpc/next/ssrPrepass';
import type { AppRouter } from '../server/routers/_app';
function getBaseUrl() {
if (typeof window !== 'undefined') return '';
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
return `http://localhost:${process.env.PORT ?? 3000}`;
}
export const trpc = createTRPCNext<AppRouter>({
ssr: true,
ssrPrepass,
config({ ctx }) {
if (typeof window !== 'undefined') {
return {
links: [httpBatchLink({ url: '/api/trpc' })],
};
}
return {
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
headers() {
if (!ctx?.req?.headers) return {};
return { cookie: ctx.req.headers.cookie };
},
}),
],
};
},
});
SSG with createServerSideHelpers and getStaticProps
import { createServerSideHelpers } from '@trpc/react-query/server';
import type {
GetStaticPaths,
GetStaticPropsContext,
InferGetStaticPropsType,
} from 'next';
import superjson from 'superjson';
import { appRouter } from '../../server/routers/_app';
import { trpc } from '../../utils/trpc';
export async function getStaticProps(
context: GetStaticPropsContext<{ id: string }>,
) {
const helpers = createServerSideHelpers({
router: appRouter,
ctx: {},
transformer: superjson,
});
const id = context.params?.id as string;
await helpers.post.byId.prefetch({ id });
return {
props: {
trpcState: helpers.dehydrate(),
id,
},
revalidate: 1,
};
}
export const getStaticPaths: GetStaticPaths = async () => {
return { paths: [], fallback: 'blocking' };
};
export default function PostPage(
props: InferGetStaticPropsType<typeof getStaticProps>,
) {
const { id } = props;
const postQuery = trpc.post.byId.useQuery({ id });
if (postQuery.status !== 'success') return <>Loading...</>;
return <h1>{postQuery.data.title}</h1>;
}
Server-side helpers with getServerSideProps
import { createServerSideHelpers } from '@trpc/react-query/server';
import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from 'next';
import superjson from 'superjson';
import { appRouter } from '../../server/routers/_app';
import { trpc } from '../../utils/trpc';
export async function getServerSideProps(
context: GetServerSidePropsContext<{ id: string }>,
) {
const helpers = createServerSideHelpers({
router: appRouter,
ctx: {},
transformer: superjson,
});
const id = context.params?.id as string;
await helpers.post.byId.prefetch({ id });
return {
props: {
trpcState: helpers.dehydrate(),
id,
},
};
}
export default function PostPage(
props: InferGetServerSidePropsType<typeof getServerSideProps>,
) {
const { id } = props;
const postQuery = trpc.post.byId.useQuery({ id });
if (postQuery.status !== 'success') return <>Loading...</>;
return <h1>{postQuery.data.title}</h1>;
}
SSR response caching
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import { ssrPrepass } from '@trpc/next/ssrPrepass';
import type { AppRouter } from '../server/routers/_app';
export const trpc = createTRPCNext<AppRouter>({
ssr: true,
ssrPrepass,
config() {
return {
links: [httpBatchLink({ url: '/api/trpc' })],
};
},
responseMeta(opts) {
const { clientErrors } = opts;
if (clientErrors.length) {
return { status: clientErrors[0].data?.httpStatus ?? 500 };
}
const ONE_DAY_IN_SECONDS = 60 * 60 * 24;
return {
headers: new Headers([
[
'cache-control',
`s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,
],
]),
};
},
});
CORS on the API handler
import { createNextApiHandler } from '@trpc/server/adapters/next';
import type { NextApiRequest, NextApiResponse } from 'next';
import { appRouter } from '../../../server/routers/_app';
const nextApiHandler = createNextApiHandler({
router: appRouter,
createContext: () => ({}),
});
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST');
res.setHeader('Access-Control-Allow-Headers', '*');
if (req.method === 'OPTIONS') {
res.writeHead(200);
return res.end();
}
return nextApiHandler(req, res);
}
Limiting batch size with maxBatchSize
import { createNextApiHandler } from '@trpc/server/adapters/next';
import { appRouter } from '../../../server/routers/_app';
export default createNextApiHandler({
router: appRouter,
createContext: () => ({}),
maxBatchSize: 10,
});
Requests batching more than maxBatchSize operations are rejected with a 400 Bad Request error. Set maxItems on your client's httpBatchLink to the same value to avoid exceeding the limit.
Common Mistakes
Using ssr: true without understanding implications
Enabling ssr: true imports react-dom and runs ssrPrepass on every request, rendering the component tree repeatedly until no queries are fetching. This adds latency and server load. For better control, keep ssr: false (the default) and use createServerSideHelpers in getServerSideProps or getStaticProps to selectively prefetch only the queries you need.
SSR prepass renders multiple times
The SSR prepass loop re-renders the component tree repeatedly until all queries resolve. This is by design but causes performance issues with expensive renders. Keep SSR-rendered pages lightweight, or switch to selective prefetching with server-side helpers.
Mixing App Router and Pages Router patterns
App Router uses fetchRequestHandler, createTRPCOptionsProxy, and @trpc/tanstack-react-query. Pages Router uses createNextApiHandler, createTRPCNext, and @trpc/next/@trpc/react-query. Applying App Router patterns (like HydrationBoundary or prefetchQuery) in Pages Router, or vice versa, produces non-functional code.
Forgetting to return trpcState from getStaticProps/getServerSideProps
When using createServerSideHelpers, you must return trpcState: helpers.dehydrate() in props. Without this, the prefetched data is lost and queries re-fetch on the client.
// WRONG
return { props: { id } }; // missing trpcState!
// CORRECT
return { props: { trpcState: helpers.dehydrate(), id } };
See Also
- [server-setup] -- initTRPC, routers, procedures, context
- [client-setup] -- vanilla tRPC client, links configuration
- [nextjs-app-router] -- if migrating to or starting with App Router
- [react-query-classic-migration] -- migrating from @trpc/react-query to @trpc/tanstack-react-query
Files
1- SKILL.md
7578d6cd4710.4 KB
Agent reviews
0No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.
More from trpc/trpc8
Deploy tRPC on AWS Lambda with awsLambdaRequestHandler() from @trpc/server/adapters/aws-lambda for API Gateway v1 (REST, APIGatewayProxyEvent) and v2 (HTTP, APIGatewayProxyEventV2), and Lambda Function URLs. Enable response streaming with awsLambdaStreamingRequestHandler() wrapped in awslambda.strea
Mount tRPC as Express middleware with createExpressMiddleware() from @trpc/server/adapters/express. Access Express req/res in createContext via CreateExpressContextOptions. Mount at a path prefix like app.use('/trpc', ...). Avoid global express.json() conflicting with tRPC body parsing for FormData.
Mount tRPC as a Fastify plugin with fastifyTRPCPlugin from @trpc/server/adapters/fastify. Configure prefix, trpcOptions (router, createContext, onError). Enable WebSocket subscriptions with useWSS and @fastify/websocket. Set routerOptions.maxParamLength for batch requests. Requires Fastify v5+. Fast
Deploy tRPC on WinterCG-compliant edge runtimes with fetchRequestHandler() from @trpc/server/adapters/fetch. Supports Cloudflare Workers, Deno Deploy, Vercel Edge Runtime, Astro, Remix, SolidStart. FetchCreateContextFnOptions provides req (Request) and resHeaders (Headers) for context creation. The
Mount tRPC on Node.js built-in HTTP server with createHTTPServer() from @trpc/server/adapters/standalone, createHTTPHandler() for custom http.createServer, createHTTP2Handler() for HTTP/2 with TLS. Configure basePath to slice URL prefix, CORS via the cors npm package passed as middleware option. Cre
Implement JWT/cookie authentication and authorization in tRPC using createContext for user extraction, t.middleware with opts.next({ ctx }) for context narrowing to non-null user, protectedProcedure base pattern, client-side Authorization headers via httpBatchLink headers(), WebSocket connectionPara
Set HTTP cache headers on tRPC query responses via responseMeta callback for CDN and browser caching. Configure Cache-Control, s-maxage, stale-while-revalidate. Handle caching with batching and authenticated requests. Avoid caching mutations, errors, and authenticated responses.
Create a vanilla tRPC client with createTRPCClient<AppRouter>(), configure link chain with httpBatchLink/httpLink, dynamic headers for auth, transformer on links (not client constructor). Infer types with inferRouterInputs and inferRouterOutputs. AbortController signal support. TRPCClientError typin
Related frontend skillsscan passed
React and Next.js performance optimization patterns adapted from Vercel Engineering's React Best Practices (https://github.com/vercel-labs/agent-skills). Organizes 70+ rules across 8 priority categories — waterfalls, bundle size, server-side, client fetching, re-render, rendering, JS micro-perf, adv
Build UIs with @nuxt/ui v4 — 125+ accessible Vue components with Tailwind CSS theming. Use when creating interfaces, customizing themes to match a brand, building forms, or composing layouts like dashboards, docs sites, and chat interfaces.
Design shotgun: generate multiple AI design variants, open a comparison board, collect structured feedback, and iterate. (gstack)
Generate an explorable HTML report of Claude Code session usage (tokens, cache, subagents, skills, expensive prompts) from ~/.claude/projects transcripts.
Polishes the surfaces, icons and motion in your project with exact values for border radius, optical alignment, shadows, icon states and animation.
Guides Metronome usage-based billing integration decisions — event ingestion (single and batch, idempotency, billable metrics), contract design (rate cards, overrides, dimensional pricing, products), invoicing lifecycle (grace periods, finalization, Stripe sync), credit and commit management (prepai