Skip to content

@trpc/next

The tRPC Next.js library

nextjs-pages-router

486 linesSource

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.

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.

plaintext
.
├── 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

shell
npm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query@latest zod

The 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:

tsconfig.json
"compilerOptions": {
+   "strict": true
}

If strict mode is too harsh, you'll at least want to enable strictNullChecks:

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:

View sample backend
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;

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;

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

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.

utils/trpc.ts
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:

pages/_app.tsx
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

pages/index.tsx
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

utils/trpc.ts
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.

ts
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.

ts
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.

ts
// 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

pages/posts/[id].tsx
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 RouterPages Router
Recommended forNew projectsExisting Pages Router projects
Data fetchingServer Components, prefetchQuerygetServerSideProps, getStaticProps, SSR via HOC
Server adapterFetch adapterNext.js adapter
Client package@trpc/tanstack-react-query@trpc/next + @trpc/react-query
Provider setupManual QueryClientProvider + TRPCProviderAutomatic 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
  • 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