# Set up with Next.js Pages Router

> 

> [!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.md) 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.md) (`@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.md), 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`](#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

```package-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.md) and [Backend usage docs](https://trpc.io/docs/server/overview.md) for tRPC information
- the [Next.js Adapter docs](https://trpc.io/docs/server/adapters/nextjs.md) for mounting tRPC within your Next.js server.

<details>
<summary>View sample backend</summary>

```ts 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 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 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](#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 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 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 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.md) 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.md).

```tsx 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.md) or [`getStaticProps`](https://trpc.io/docs/client/nextjs/pages-router/ssg.md).

## 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.md) 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.md#fetch-data-in-getstaticprops) does. Then read it back with a hook:

```ts 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](#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](#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.md)                                                |
| `responseMeta`                                  | Set the status and headers in [`getServerSideProps`](https://trpc.io/docs/client/nextjs/pages-router/ssr.md#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.md) |
| `abortOnUnmount` in `config`                    | `trpc: { abortOnUnmount: true }` on each query. See [Aborting Procedure Calls](https://trpc.io/docs/client/nextjs/pages-router/aborting-procedure-calls.md)                          |
| `queryClientConfig`                             | The options you pass to `new QueryClient()` in `makeQueryClient`                                                                       |

The [migration guide](https://trpc.io/docs/client/tanstack-react-query/migrating.md) 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](#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.md), [middlewares](https://trpc.io/docs/server/middlewares.md), and [error handling](https://trpc.io/docs/server/error-handling.md).

You can also find information about [queries, mutations and invalidation](https://trpc.io/docs/client/tanstack-react-query/usage.md) in the TanStack React Query docs.
