nextjs-pages-router
Set up tRPC in Next.js Pages Router with createNextApiHandler, createTRPCNext (config, transformer, ssr, ssrPrepass, responseMeta) and the withTRPC HOC over @trpc/react-query hooks. Prefetch selected pages with createServerSideHelpers in getServerSideProps or getStaticProps and return trpcState, or SSR every query with ssr true, forwarding request headers.
Set up with Next.js Pages Router
From https://trpc.io/docs/client/nextjs/pages-router/setup
Caution
This guide is for the Next.js Pages Router. If you are using the Next.js App Router, see the App Router setup guide instead.
Recommended file structure
We recommend a file structure like this one, although it is not enforced by tRPC. This is what you'll see in our examples. The rest of this page will take you through the process of adding tRPC in to this structure.
.
├── prisma # <-- if prisma is added
│ └── [..]
├── src
│ ├── pages
│ │ ├── _app.tsx # <-- add `withTRPC()`-HOC here
│ │ ├── api
│ │ │ └── trpc
│ │ │ └── [trpc].ts # <-- tRPC HTTP handler
│ │ └── [..]
│ ├── server
│ │ ├── routers
│ │ │ ├── _app.ts # <-- main app router
│ │ │ ├── post.ts # <-- sub routers
│ │ │ └── [..]
│ │ ├── context.ts # <-- create app context
│ │ └── trpc.ts # <-- procedure helpers
│ └── utils
│ └── trpc.ts # <-- your typesafe tRPC hooks
└── [..]Add tRPC to existing Next.js project
1. Install deps
npm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query@latest zodThe Next.js integration is actually a combination of our React Query Integration and some Next.js specific integrations.
2. Enable strict mode
If you want to use Zod for input validation, make sure you have enabled strict mode in your tsconfig.json:
"compilerOptions": {
+ "strict": true
}If strict mode is too harsh, you'll at least want to enable strictNullChecks:
"compilerOptions": {
+ "strictNullChecks": true
}3. Create a tRPC router
Initialize your tRPC backend in src/server/trpc.ts using the initTRPC function, and create your first router. We're going to make a simple "hello world" router and procedure here - but for deeper information on creating your tRPC API you should refer to:
- the Quickstart guide and Backend usage docs for tRPC information
- the Next.js Adapter docs for mounting tRPC within your Next.js server.
View sample backend
import { initTRPC } from '@trpc/server';
// Avoid exporting the entire t-object
// since it's not very descriptive.
// For instance, the use of a t variable
// is common in i18n libraries.
const t = initTRPC.create();
// Base router and procedure helpers
export const router = t.router;
export const procedure = t.procedure;import { z } from 'zod';
import { procedure, router } from '../trpc';
export const appRouter = router({
hello: procedure
.input(
z.object({
text: z.string(),
}),
)
.query((opts) => {
return {
greeting: `hello ${opts.input.text}`,
};
}),
});
// export type definition of API
export type AppRouter = typeof appRouter;import * as trpcNext from '@trpc/server/adapters/next';
import { appRouter } from '../../../server/routers/_app';
// export API handler
// @link https://trpc.io/docs/server/adapters
export default trpcNext.createNextApiHandler({
router: appRouter,
createContext: () => ({}),
});Note
The backend above is using the recommended file structure, but you can keep it simple and put everything in an API handler directly if you prefer.
4. Create tRPC hooks
Use the createTRPCNext function to create a set of strongly-typed hooks from your API's type signature.
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import type { AppRouter } from '../server/routers/_app';
function getBaseUrl() {
if (typeof window !== 'undefined')
// browser should use relative path
return '';
if (process.env.VERCEL_URL)
// reference for vercel.com
return `https://${process.env.VERCEL_URL}`;
if (process.env.RENDER_INTERNAL_HOSTNAME)
// reference for render.com
return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
// assume localhost
return `http://localhost:${process.env.PORT ?? 3000}`;
}
export const trpc = createTRPCNext<AppRouter>({
config(config) {
return {
links: [
httpBatchLink({
/**
* If you want to use SSR, you need to use the server's full URL
* @see https://trpc.io/docs/client/nextjs/pages-router/ssr
**/
url: `${getBaseUrl()}/api/trpc`,
// You can pass any HTTP headers you wish here
async headers() {
return {
// authorization: getAuthCookie(),
};
},
}),
],
};
},
/**
* @see https://trpc.io/docs/client/nextjs/pages-router/ssr
**/
ssr: false,
});Note
createTRPCNext does not work with the tRPC-v9 interop mode. If you are migrating from v9 using interop, you should continue using the old way of initializing tRPC.
5. Configure _app.tsx
Wrap your root app page in the trpc.withTRPC HOC, similar to this:
import type { AppType } from 'next/app';
import { trpc } from '../utils/trpc';
const MyApp: AppType = ({ Component, pageProps }) => {
return <Component {...pageProps} />;
};
export default trpc.withTRPC(MyApp);6. Make an API request
You're all set!
You can now use the React hooks you have just created to invoke your API. For more detail see the React Query Integration
import { trpc } from '../utils/trpc';
export default function IndexPage() {
const hello = trpc.hello.useQuery({ text: 'client' });
if (!hello.data) {
return <div>Loading...</div>;
}
return (
<div>
<p>{hello.data.greeting}</p>
</div>
);
}createTRPCNext() options
config-callback
The config-argument is a function that returns an object that configures the tRPC and React Query clients. This function receives an object with an optional ctx property (of type NextPageContext) that gives you access to the Next.js req object during server-side rendering. The returned value can contain the following properties:
- Required:
- links to customize the flow of data between tRPC Client and the tRPC Server. Read more.
- Optional:
- queryClientConfig: a configuration object for the React Query QueryClient used internally by the tRPC React hooks: QueryClient docs
- queryClient: a React Query QueryClient instance
- Note: You can only provide either a queryClient or a queryClientConfig.
- abortOnUnmount: determines if in-flight requests will be cancelled on component unmount. This defaults to false.
transformer
If your router uses a data transformer, pass it here as well as to your links. tRPC uses it to read the trpcState that SSR, SSG and the server-side helpers send to the page.
overrides: (default: undefined)
Configure overrides for React Query's hooks.
ssr-boolean (default: false)
Whether tRPC should await queries when server-side rendering a page. Defaults to false. Enabling it requires the ssrPrepass option, imported from @trpc/next/ssrPrepass, and you can pass a function of the request instead of true. See SSR.
responseMeta-callback
Ability to set response headers and HTTP status when server-side rendering, so it requires ssr. It receives the request's ctx and the clientErrors of the queries that failed. Return headers as a plain object: a Headers instance is ignored here.
Example
import { createTRPCNext } from '@trpc/next';
import { ssrPrepass } from '@trpc/next/ssrPrepass';
import type { AppRouter } from '../server/routers/_app';
export const trpc = createTRPCNext<AppRouter>({
config(config) {
return {
links: [
/* [...] */
],
};
},
ssr: true,
ssrPrepass,
responseMeta(opts) {
const error = opts.clientErrors[0];
if (error) {
// propagate the HTTP status of the first failed query
return { status: error.data?.httpStatus ?? 500 };
}
return {
headers: { 'cache-control': 's-maxage=1, stale-while-revalidate=86400' },
};
},
});Common mistakes
- Turning on ssr: true without weighing the cost. It adds a getInitialProps to your app, which turns off Next.js' Automatic Static Optimization and doesn't mix well with getServerSideProps. It also renders the page repeatedly until no query is fetching, so every query delays the response. To prefetch only some pages, keep ssr: false and use the server-side helpers in getServerSideProps or getStaticProps.
- Not forwarding the request's headers during SSR. The server-side render calls your API without the user's cookies unless the link's headers() passes on ctx.req.headers, and it needs an absolute URL. See SSR.
- Using Suspense with ssr: true. If a query fails while rendering on the server, the whole page fails, even inside an <ErrorBoundary />.
- Returning the dehydrated state under another key. withTRPC only reads pageProps.trpcState, so return { props: { trpcState: helpers.dehydrate() } }. Otherwise the client fetches every query again.
- Mixing in App Router setup. createTRPCNext and withTRPC are for the Pages Router. In the App Router, follow the App Router setup instead.
Next steps
Browse the rest of the docs to learn more about things like authorization, middlewares, and error handling.
You can also find information about queries and mutations now that you're using @trpc/react-query.
Server-Side Helpers
From https://trpc.io/docs/client/nextjs/pages-router/server-side-helpers
The server-side helpers provide you with a set of helper functions that you can use to prefetch queries on the server. This is useful for SSG, but also for SSR if you opt not to use ssr: true.
Prefetching via the server-side helpers allows populating the query cache on the server, which means that these queries do not have to fetch on the client initially.
There are 2 ways to use the server-side helpers.
1. Internal router
This method is used when you have direct access to your tRPC router, e.g., when developing a monolithic Next.js application.
Using the helpers makes tRPC call your procedures directly on the server, without an HTTP request, similar to server-side calls. That also means that you don't have the request and response at hand like you usually do. Make sure you're instantiating the server-side helpers with a context without req & res, which are typically filled via the context creation. We recommend the concept of "inner" and "outer" context in that scenario.
import { createServerSideHelpers } from '@trpc/react-query/server';
import { createContext } from './server/context';
import { appRouter } from './server/routers/_app';
import superjson from 'superjson';
const helpers = createServerSideHelpers({
router: appRouter,
ctx: await createContext(),
transformer: superjson,
});2. External router
This method is used when you don't have direct access to your tRPC router, e.g., when developing a Next.js application and a standalone API hosted separately.
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import { createServerSideHelpers } from '@trpc/react-query/server';
import type { AppRouter } from './server/router';
import superjson from 'superjson';
const proxyClient = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/api/trpc',
}),
],
});
const helpers = createServerSideHelpers({
client: proxyClient,
});Helpers usage
The server-side helpers methods return an object that mirrors your router structure, with all of your routers as keys. However, rather than useQuery and useMutation, you get prefetch, fetch, prefetchInfinite, and fetchInfinite functions.
The primary difference between prefetch and fetch is that fetch acts much like a normal function call, returning the result of the query, whereas prefetch does not return the result and never throws - if you need that behavior, use fetch instead. Instead, prefetch will add the query to the cache, which you then dehydrate and send to the client.
// In getServerSideProps / getStaticProps:
const props = {
// very important - use `trpcState` as the key
trpcState: helpers.dehydrate(),
};The rule of thumb is prefetch for queries that you know you'll need on the client, and fetch for queries that you want to use the result of on the server.
The functions are all wrappers around react-query functions. Please check out their docs to learn more about them in detail.
Info
For a full example, see our E2E SSG test example
Next.js Example
import { createServerSideHelpers } from '@trpc/react-query/server';
import { appRouter } from './server/routers/_app';
import { trpc } from './utils/trpc';
import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next';
import superjson from 'superjson';
export async function getServerSideProps(
context: GetServerSidePropsContext<{ id: string }>,
) {
const helpers = createServerSideHelpers({
router: appRouter,
ctx: {},
transformer: superjson,
});
const id = context.params?.id as string;
/*
* Prefetching the `post.byId` query.
* `prefetch` does not return the result and never throws - if you need that behavior, use `fetch` instead.
*/
await helpers.post.byId.prefetch({ id });
// Make sure to return { props: { trpcState: helpers.dehydrate() } }
return {
props: {
trpcState: helpers.dehydrate(),
id,
},
};
}
export default function PostViewPage(
props: InferGetServerSidePropsType<typeof getServerSideProps>,
) {
const { id } = props;
const postQuery = trpc.post.byId.useQuery({ id });
if (postQuery.status !== 'success') {
// won't happen since the query has been prefetched
return <>Loading...</>;
}
const { data } = postQuery;
return (
<>
<h1>{data.title}</h1>
<em>Created {data.createdAt.toLocaleDateString()}</em>
<p>{data.text}</p>
<h2>Raw data:</h2>
<pre>{JSON.stringify(data, null, 4)}</pre>
</>
);
}Choosing between App Router and Pages Router
| App Router | Pages Router | |
|---|---|---|
| Recommended for | New projects | Existing Pages Router projects |
| Data fetching | Server Components, prefetchQuery | getServerSideProps, getStaticProps, SSR via HOC |
| Server adapter | Fetch adapter | Next.js adapter |
| Client package | @trpc/tanstack-react-query | @trpc/next + @trpc/react-query |
| Provider setup | Manual QueryClientProvider + TRPCProvider | Automatic via withTRPC() HOC |
Tip
If you're starting a new project, we recommend the App Router. If you have an existing Pages Router project, the Pages Router integration works well and is fully supported.
From https://trpc.io/docs/client/nextjs#choosing-between-app-router-and-pages-router
References
- Server-Side Rendering: Render every tRPC query on the server with ssr: true and ssrPrepass, forward request headers, and cache SSR responses with responseMeta
- Static Site Generation: Prefetch tRPC queries in getStaticProps with the server-side helpers and stop the client from refetching them
- Next.js Adapter: Serve tRPC from a Next.js API route with createNextApiHandler, or from an App Router route handler with fetchRequestHandler
- Aborting Procedure Calls: Cancel in-flight requests when a component unmounts with abortOnUnmount, globally or per request
- useQuery(): Fetch data with the classic trpc.procedure.useQuery() hook, its tRPC-specific options and streaming
- useMutation(): Call mutations with the classic trpc.procedure.useMutation() hook
- useUtils: Invalidate, refetch, prefetch and update cached query data with the classic trpc.useUtils() helpers
Related skills
- server-setup (https://trpc.io/docs/server/overview): npx @tanstack/intent@latest load @trpc/server#server-setup
- client-setup (https://trpc.io/docs/client/vanilla/setup): npx @tanstack/intent@latest load @trpc/client#client-setup