---
name: nextjs-pages-router
description: Set up tRPC in Next.js Pages Router with createNextApiHandler and @trpc/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 `@trpc/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 @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query@latest 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 '@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;
```

<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 '@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: () => ({}),
});
```

</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 '@tanstack/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 '@trpc/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 '@tanstack/react-query';
import { HydrationBoundary, QueryClientProvider } from '@tanstack/react-query';
import { createTRPCClient, httpBatchLink } from '@trpc/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 '@tanstack/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 '@tanstack/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:

| `@trpc/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` (`@trpc/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 `@trpc/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 `@trpc/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 '@trpc/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 '@trpc/client';
import { createTRPCOptionsProxy } from '@trpc/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 `@trpc/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 '@tanstack/react-query';
import { createTRPCOptionsProxy } from '@trpc/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**  | `@trpc/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`
