@trpc/next
Version:
496 lines (381 loc) • 24.8 kB
Markdown
---
name: nextjs-pages-router
description: Set up tRPC in Next.js Pages Router with createNextApiHandler and /tanstack-react-query. Covers a QueryClient factory, TRPCProvider, QueryClientProvider and HydrationBoundary in _app, useTRPC() with useQuery in pages, and prefetching a page's queries in getServerSideProps or getStaticProps with createTRPCOptionsProxy and dehydrate, returned as trpcState. Replaces the deprecated createTRPCNext, withTRPC, ssr true, ssrPrepass and createServerSideHelpers.
requires:
- server-setup
- client-setup
sources:
- www/v11/docs/client/nextjs/pages-router/setup.md
- www/v11/docs/client/nextjs/pages-router/server-side-helpers.md
- www/v11/docs/client/nextjs.md
- www/v11/docs/client/nextjs/pages-router/ssr.md
- www/v11/docs/client/nextjs/pages-router/ssg.md
- www/v11/docs/server/adapters/nextjs.md
- www/v11/docs/client/nextjs/pages-router/aborting-procedure-calls.md
- www/v11/docs/client/tanstack-react-query/usage.md
- www/v11/docs/client/tanstack-react-query/migrating.md
metadata:
type: framework
library: trpc
library_version: 11.25.1
---
<!-- Generated from www/v11/docs/client/nextjs/pages-router/setup.md by www/build/skills.ts. Edit the docs instead. -->
# Set up with Next.js Pages Router
From https://trpc.io/docs/client/nextjs/pages-router/setup
> [!WARNING] 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](https://trpc.io/docs/client/nextjs/app-router-setup) instead.
The Pages Router uses the same client as the App Router: the [TanStack React Query integration](https://trpc.io/docs/client/tanstack-react-query) (`@trpc/tanstack-react-query`). You mount its provider in `_app`, and prefetch a page's queries in `getServerSideProps` or `getStaticProps` when you want them in the first HTML.
> [!WARNING] Deprecated
> `createTRPCNext`, `withTRPC` and `ssr: true` from `/next` are deprecated. They're built on the [classic React Query integration](https://trpc.io/docs/client/react), which only gets bug fixes and will be removed in the next major version of tRPC. To move an existing app, see [Moving from `createTRPCNext`](https://trpc.io/docs/client/nextjs/pages-router/setup#moving-from-createtrpcnext).
## Recommended file structure
We recommend a file structure like this one, although it is not enforced by tRPC. The rest of this page will take you through the process of adding tRPC in to this structure.
```graphql
.
├── prisma # <-- if prisma is added
│ └── [..]
├── src
│ ├── pages
│ │ ├── _app.tsx # <-- mount the providers 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
│ ├── query-client.ts # <-- shared QueryClient factory
│ └── trpc.ts # <-- your typesafe tRPC hooks
└── [..]
```
## Add tRPC to existing Next.js project
### 1. Install deps
```bash
npm install /server @trpc/client /tanstack-react-query @tanstack/react-query zod
```
### 2. Enable strict mode
If you want to use Zod for input validation, make sure you have enabled strict mode in your `tsconfig.json`:
```diff title="tsconfig.json"
"compilerOptions": {
+ "strict": true
}
```
If strict mode is too harsh, you'll at least want to enable `strictNullChecks`:
```diff title="tsconfig.json"
"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](https://trpc.io/docs/quickstart) and [Backend usage docs](https://trpc.io/docs/server/overview) for tRPC information
- the [Next.js Adapter docs](https://trpc.io/docs/server/adapters/nextjs) for mounting tRPC within your Next.js server.
<details>
<summary>View sample backend</summary>
```ts no-check title="server/trpc.ts"
import { initTRPC } from '/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;
```
<br />
```ts no-check title="server/routers/_app.ts"
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;
```
<br />
```ts no-check title="pages/api/trpc/[trpc].ts"
import * as trpcNext from '/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: () => ({}),
});
```
</details>
> [!NOTE]
> The backend above is using the [recommended file structure](https://trpc.io/docs/client/nextjs/pages-router/setup#recommended-file-structure), but you can keep it simple and put everything in [an API handler directly](https://github.com/trpc/trpc/blob/db643ba428f4eaf75fcfeef13a53d29f446ee567/examples/next-minimal-starter/src/pages/api/trpc/%5Btrpc%5D.ts) if you prefer.
### 4. Create a Query Client factory
Create `utils/query-client.ts`. Both `_app` and the pages that prefetch on the server use it.
```ts no-check title="utils/query-client.ts"
import { QueryClient } from '/react-query';
export function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
// keep server-rendered data fresh for a while, so the
// client doesn't fetch it again as soon as the page loads
staleTime: 30 * 1000,
},
},
});
}
```
### 5. Create tRPC hooks
Use `createTRPCContext` to create a provider and a `useTRPC` hook from your API's type signature.
```ts no-check title="utils/trpc.ts"
import { createTRPCContext } from '/tanstack-react-query';
import type { AppRouter } from '../server/routers/_app';
export const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>();
```
### 6. Configure `_app.tsx`
Create the query client and the tRPC client once per app, and wrap every page in the providers. `HydrationBoundary` puts the queries a page prefetched on the server, which it returns as `trpcState`, into the cache.
```tsx no-check title="pages/_app.tsx"
import type { DehydratedState } from '/react-query';
import { HydrationBoundary, QueryClientProvider } from '/react-query';
import { createTRPCClient, httpBatchLink } from '/client';
import type { AppProps } from 'next/app';
import { useState } from 'react';
import type { AppRouter } from '../server/routers/_app';
import { makeQueryClient } from '../utils/query-client';
import { TRPCProvider } from '../utils/trpc';
export default function App({
Component,
pageProps,
}: AppProps<{ trpcState?: DehydratedState }>) {
const [queryClient] = useState(makeQueryClient);
const [trpcClient] = useState(() =>
createTRPCClient<AppRouter>({
links: [
httpBatchLink({
// transformer: superjson, <-- if you use a data transformer
url: '/api/trpc',
}),
],
}),
);
return (
<QueryClientProvider client={queryClient}>
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
<HydrationBoundary state={pageProps.trpcState}>
<Component {...pageProps} />
</HydrationBoundary>
</TRPCProvider>
</QueryClientProvider>
);
}
```
The client only calls the API from the browser, so a relative URL is enough. On the server, pages call your procedures directly, as [Server-Side Rendering](https://trpc.io/docs/client/nextjs/pages-router/ssr) shows.
### 7. Make an API request
You're all set! Get the options for a query from `useTRPC()`, and pass them to TanStack React Query's hooks. For more, see [TanStack React Query usage](https://trpc.io/docs/client/tanstack-react-query/usage).
```tsx no-check title="pages/index.tsx"
import { useQuery } from '/react-query';
import { useTRPC } from '../utils/trpc';
export default function IndexPage() {
const trpc = useTRPC();
const helloQuery = useQuery(trpc.hello.queryOptions({ text: 'client' }));
switch (helloQuery.status) {
case 'pending':
return <p>Loading...</p>;
case 'error':
return <p>Error: {helloQuery.error.message}</p>;
case 'success':
return <p>{helloQuery.data.greeting}</p>;
}
}
```
This page fetches in the browser. To put the data in the first HTML, prefetch it in [`getServerSideProps`](https://trpc.io/docs/client/nextjs/pages-router/ssr) or [`getStaticProps`](https://trpc.io/docs/client/nextjs/pages-router/ssg).
## Using a data transformer
Next.js sends page props as JSON, and checks that they are. So without a transformer, the queries you prefetch can only hold JSON values, in their inputs as well as their results: a `Date` or an `undefined` field makes `getServerSideProps` fail.
If your router uses a [data transformer](https://trpc.io/docs/server/data-transformers) such as superjson, run the whole prefetched cache through it. In `getServerSideProps` or `getStaticProps`, return `trpcState: superjson.serialize(dehydrate(queryClient))`, as the [SSG example](https://trpc.io/docs/client/nextjs/pages-router/ssg#fetch-data-in-getstaticprops) does. Then read it back with a hook:
```ts no-check title="utils/use-trpc-state.ts"
import type { DehydratedState } from '/react-query';
import { useMemo } from 'react';
import superjson from 'superjson';
export type TRPCState = ReturnType<typeof superjson.serialize>;
export function useTRPCState(trpcState: TRPCState | undefined) {
return useMemo(
() => trpcState && superjson.deserialize<DehydratedState>(trpcState),
[trpcState],
);
}
```
In the `_app` from [step 6](https://trpc.io/docs/client/nextjs/pages-router/setup#6-configure-_apptsx), change three things:
- Type the props as `AppProps<{ trpcState?: TRPCState }>`.
- Pass `transformer: superjson` to `httpBatchLink`.
- Call `const trpcState = useTRPCState(pageProps.trpcState)`, and pass `trpcState` to `HydrationBoundary` instead of `pageProps.trpcState`.
## Moving from `createTRPCNext`
`createTRPCNext` combined the classic React Query hooks with a higher-order component for `_app`. Here's what replaces each part:
| `/next` | `@trpc/tanstack-react-query` |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `createTRPCNext<AppRouter>({ config })` | `createTRPCContext<AppRouter>()`, plus a `createTRPCClient` with your links in `_app` |
| `trpc.withTRPC(MyApp)` | `QueryClientProvider`, `TRPCProvider` and `HydrationBoundary` in `_app`, as in [step 6](https://trpc.io/docs/client/nextjs/pages-router/setup#6-configure-_apptsx) |
| `trpc.post.byId.useQuery(input)` | `useQuery(trpc.post.byId.queryOptions(input))`, with `trpc` from `useTRPC()` |
| `trpc.useUtils()` | `useQueryClient()` with `trpc.post.byId.queryKey()` or `queryFilter()` |
| `ssr: true` and `ssrPrepass` | No direct replacement. Prefetch each page's queries in [`getServerSideProps`](https://trpc.io/docs/client/nextjs/pages-router/ssr) |
| `responseMeta` | Set the status and headers in [`getServerSideProps`](https://trpc.io/docs/client/nextjs/pages-router/ssr#set-the-status-and-cache-headers) |
| `config({ ctx })` forwarding the request headers | Pass a context built from `context.req` to `createTRPCOptionsProxy` in `getServerSideProps` |
| `createServerSideHelpers` (`/react-query/server`) | `createTRPCOptionsProxy` with a query client, then `dehydrate(queryClient)`. See [Server-Side Helpers](https://trpc.io/docs/client/nextjs/pages-router/server-side-helpers) |
| `abortOnUnmount` in `config` | `trpc: { abortOnUnmount: true }` on each query. See [Aborting Procedure Calls](https://trpc.io/docs/client/nextjs/pages-router/aborting-procedure-calls) |
| `queryClientConfig` | The options you pass to `new QueryClient()` in `makeQueryClient` |
The [migration guide](https://trpc.io/docs/client/tanstack-react-query/migrating) covers the hooks in detail, and has a codemod for them that's still a work in progress.
`ssr: true` rendered the page over and over until no query was fetching. With `getServerSideProps`, you pick which queries run on the server, and Next.js keeps [Automatic Static Optimization](https://nextjs.org/docs/pages/building-your-application/rendering/automatic-static-optimization) for pages that don't need them.
## Common mistakes
- **Sharing one `QueryClient` across requests.** Make it in `_app` with `useState`, and make a new one in each `getServerSideProps` or `getStaticProps`. A query client at module scope would hand one user's data to the next.
- **Returning the dehydrated state under another key.** `_app` reads `pageProps.trpcState`, so return `{ props: { trpcState: dehydrate(queryClient) } }`. Otherwise the client fetches every query again.
- **Prefetching with a different input than the page uses.** The input is part of the query key, so the page misses the prefetched data and fetches again.
- **Prefetching values JSON can't hold.** Next.js sends page props as JSON, so a `Date` or an `undefined` field in a query's input or result makes the page fail. Run the prefetched cache through a [data transformer](https://trpc.io/docs/client/nextjs/pages-router/setup#using-a-data-transformer).
- **Mixing in App Router setup.** `HydrateClient` and `server-only` are for the App Router. In the Pages Router, prefetch in `getServerSideProps` or `getStaticProps` instead.
## Next steps
Browse the rest of the docs to learn more about things like [authorization](https://trpc.io/docs/server/authorization), [middlewares](https://trpc.io/docs/server/middlewares), and [error handling](https://trpc.io/docs/server/error-handling).
You can also find information about [queries, mutations and invalidation](https://trpc.io/docs/client/tanstack-react-query/usage) in the TanStack React Query docs.
## Server-Side Helpers
From https://trpc.io/docs/client/nextjs/pages-router/server-side-helpers
To run queries on the server in `getServerSideProps` or `getStaticProps`, make a query client and pass it to `createTRPCOptionsProxy` from `/tanstack-react-query`. The proxy gives you the same `queryOptions` as `useTRPC()` in a component. You prefetch them into the query client, then send its cache to the page, so the page doesn't have to fetch them again.
> [!WARNING] Deprecated
> `createServerSideHelpers` from `/react-query/server` belongs to the [classic React Query integration](https://trpc.io/docs/client/react), which only gets bug fixes and will be removed in the next major version of tRPC. Use `createTRPCOptionsProxy` instead, as below.
### Create the proxy
There are two ways to create it, depending on where your router runs.
#### 1. Internal router
Use this when your router is in the same Next.js app. The proxy calls your procedures directly, with no HTTP request, as [server-side calls](https://trpc.io/docs/server/server-side-calls) do. So pass it a context: build it from `context.req` in `getServerSideProps`, as [SSR](https://trpc.io/docs/client/nextjs/pages-router/ssr#build-the-context-from-the-request) shows. In `getStaticProps` there's no request, so the context can't need one. The ["inner" and "outer" context](https://trpc.io/docs/server/context) pattern helps here.
```ts no-check
import { createTRPCOptionsProxy } from '/tanstack-react-query';
import { createContext } from './server/context';
import { appRouter } from './server/routers/_app';
import { makeQueryClient } from './utils/query-client';
export async function getStaticProps() {
const queryClient = makeQueryClient();
const trpc = createTRPCOptionsProxy({
router: appRouter,
ctx: await createContext(),
queryClient,
});
// ...
}
```
#### 2. External router
Use this when your API runs on another server. Pass a tRPC client instead of the router:
```ts no-check
import { createTRPCClient, httpBatchLink } from '/client';
import { createTRPCOptionsProxy } from '/tanstack-react-query';
import type { AppRouter } from './server/router';
import { makeQueryClient } from './utils/query-client';
export async function getStaticProps() {
const queryClient = makeQueryClient();
const trpc = createTRPCOptionsProxy({
client: createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/api/trpc',
}),
],
}),
queryClient,
});
// ...
}
```
Make the query client and the proxy inside `getServerSideProps` or `getStaticProps`, so each call gets new ones. A query client at module scope would share one user's data with the next.
### Prefetch, fetch and dehydrate
Run the queries with the query client's methods:
- `queryClient.prefetchQuery(trpc.post.byId.queryOptions(input))` puts the result in the cache. It never throws and returns nothing.
- `queryClient.fetchQuery(trpc.post.byId.queryOptions(input))` also caches the result, but returns it, and throws if the query fails.
- `prefetchInfiniteQuery` and `fetchInfiniteQuery` do the same with `trpc.post.list.infiniteQueryOptions(input)`.
Use `prefetchQuery` for data the page shows, and `fetchQuery` when you need the result on the server, for instance to return `notFound`. Then send the cache to the page with `dehydrate`:
```ts no-check
// In getServerSideProps / getStaticProps:
const props = {
// `_app` reads this key
trpcState: dehydrate(queryClient),
};
```
These are TanStack React Query's own functions. See [their docs](https://tanstack.com/query/v5/docs/framework/react/guides/prefetching) for more.
> [!TIP]
> `loadHandles` from `/tanstack-react-query/unstable-handles` can also start queries with this proxy and return **handles** to them: `const [handles] = await loadHandles(trpc, { post: ensureEntry(trpc.post.byId, input) })`. Spread the handles into `props` next to `trpcState`, and the page reads one with `useQuery(fromHandle(trpc, props.post))`, so it can't ask for a different input than the server used. A handle is plain JSON when its input is. If the input has a `Date` or another value your transformer handles, run the handles through the transformer too, as you do the dehydrated state. See [Prefetching with handles](https://trpc.io/docs/client/tanstack-react-query/handles).
Coming from `createServerSideHelpers`, `helpers.post.byId.prefetch(input)` is now `queryClient.prefetchQuery(trpc.post.byId.queryOptions(input))`, `fetch` is `fetchQuery`, and `helpers.dehydrate()` is `dehydrate(queryClient)`. If you passed a `transformer` to the helpers, run the dehydrated state through it yourself, as [Using a data transformer](https://trpc.io/docs/client/nextjs/pages-router/setup#using-a-data-transformer) shows.
### Next.js example
```tsx no-check title="pages/posts/[id].tsx"
import { dehydrate, useQuery } from '/react-query';
import { createTRPCOptionsProxy } from '/tanstack-react-query';
import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from 'next';
import { appRouter } from '../../server/routers/_app';
import { makeQueryClient } from '../../utils/query-client';
import { useTRPC } from '../../utils/trpc';
export async function getServerSideProps(
context: GetServerSidePropsContext<{ id: string }>,
) {
const id = context.params?.id as string;
const queryClient = makeQueryClient();
const trpc = createTRPCOptionsProxy({
router: appRouter,
ctx: {},
queryClient,
});
await queryClient.prefetchQuery(trpc.post.byId.queryOptions({ id }));
return {
props: {
trpcState: dehydrate(queryClient),
id,
},
};
}
export default function PostViewPage(
props: InferGetServerSidePropsType<typeof getServerSideProps>,
) {
const trpc = useTRPC();
const postQuery = useQuery(trpc.post.byId.queryOptions({ id: props.id }));
switch (postQuery.status) {
case 'pending':
return <p>Loading...</p>;
case 'error':
return <p>Error: {postQuery.error.message}</p>;
case 'success':
return (
<article>
<h1>{postQuery.data.title}</h1>
<p>{postQuery.data.text}</p>
</article>
);
}
}
```
## Choosing between App Router and Pages Router
| | App Router | Pages Router |
| ------------------- | -------------------------------------------- | --------------------------------------------------------------- |
| **Recommended for** | New projects | Existing Pages Router projects |
| **Data fetching** | Server Components, `prefetchQuery` | `prefetchQuery` in `getServerSideProps` or `getStaticProps` |
| **Server adapter** | [Fetch adapter](https://trpc.io/docs/server/adapters/fetch) | [Next.js adapter](https://trpc.io/docs/server/adapters/nextjs) |
| **Client package** | `/tanstack-react-query` | `@trpc/tanstack-react-query` |
| **Provider setup** | `TRPCProvider` in the root layout | `TRPCProvider` and `HydrationBoundary` in `_app` |
> [!TIP]
> If you're starting a new project, we recommend the App Router. If you have an existing Pages Router project, tRPC works well with it and is fully supported.
From https://trpc.io/docs/client/nextjs#choosing-between-app-router-and-pages-router
## References
- [Server-Side Rendering](references/client-nextjs-pages-router-ssr.md): Prefetch a page's tRPC queries in getServerSideProps with createTRPCOptionsProxy, build the context from the request, and set the status and cache headers
- [Static Site Generation](references/client-nextjs-pages-router-ssg.md): Prefetch tRPC queries in getStaticProps with createTRPCOptionsProxy and dehydrate, and stop the client from refetching them
- [Next.js Adapter](references/server-adapters-nextjs.md): Serve tRPC from a Next.js API route with createNextApiHandler, or from an App Router route handler with fetchRequestHandler
- [Aborting Procedure Calls](references/client-nextjs-pages-router-aborting-procedure-calls.md): Cancel in-flight requests when a component unmounts with the abortOnUnmount option on a query
- [TanStack React Query](references/client-tanstack-react-query-usage.md): The options and key factories on the trpc proxy, such as queryOptions, infiniteQueryOptions, mutationOptions, subscriptionOptions, queryKey, queryFilter and pathFilter, plus key prefixing and type inference
- [Migrating from the classic React Client](references/client-tanstack-react-query-migrating.md): Migrating from the classic React Client
## 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`