UNPKG

@trpc/next

Version:

The tRPC Next.js library

496 lines (381 loc) • 24.8 kB
--- 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.2 --- <!-- 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`