Usage with Next.js
Next.js makes it easy for you to build your client and server together in one codebase. tRPC makes it easy to share types between them, ensuring typesafety for your application's data fetching.
If you're using tRPC in a new project, consider using one of the example projects as a starting point or for reference: tRPC Example Projects
Recommended file structure
We recommend a file structure like this one, although it is not enforced by tRPC. This is what you'll get when starting from the examples.
graphql.├── 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└── [..]
graphql.├── 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
- npm
- yarn
- pnpm
shnpm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
shnpm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
shyarn add @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
shyarn add @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
shpnpm add @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
shpnpm add @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
Two packages of note:
- @tanstack/react-query:
@trpc/react-queryprovides a thin wrapper around react-query. It is required as a peer dependency as an idiomatic way to handle client-side caching in React applications. - Zod: We will use Zod for input validation to ensure that our backend only processes requests that fit our API. You can use other validation libraries like Yup, Superstruct, io-ts if you prefer.
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.jsondiff"compilerOptions": {+ "strict": true}
tsconfig.jsondiff"compilerOptions": {+ "strict": true}
If strict mode is too harsh, you'll at least want to enable strictNullChecks:
tsconfig.jsondiff"compilerOptions": {+ "strictNullChecks": true}
tsconfig.jsondiff"compilerOptions": {+ "strictNullChecks": true}
3. Create a tRPC router
Initialize your tRPC backend using the initTRPC function and create your first router. We're going to make a simple "hello world" router and procedure here - but be sure to browse the rest of the docs for information about creating more complex APIs.
View sample backend
server/trpc.tstsimport { 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 helpersexport const router = t.router;export const procedure = t.procedure;
server/trpc.tstsimport { 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 helpersexport const router = t.router;export const procedure = t.procedure;
server/routers/_app.tstsimport { z } from 'zod';import { procedure, router } from '../trpc';export const appRouter = router({hello: procedure.input(z.object({text: z.string(),}),).query(({ input }) => {return {greeting: `hello ${input.text}`,};}),});// export type definition of APIexport type AppRouter = typeof appRouter;
server/routers/_app.tstsimport { z } from 'zod';import { procedure, router } from '../trpc';export const appRouter = router({hello: procedure.input(z.object({text: z.string(),}),).query(({ input }) => {return {greeting: `hello ${input.text}`,};}),});// export type definition of APIexport type AppRouter = typeof appRouter;
pages/api/trpc/[trpc].tstsimport * as trpcNext from '@trpc/server/adapters/next';import { appRouter } from '../../../server/routers/_app';// export API handler// @see https://trpc.io/docs/api-handlerexport default trpcNext.createNextApiHandler({router: appRouter,createContext: () => ({}),});
pages/api/trpc/[trpc].tstsimport * as trpcNext from '@trpc/server/adapters/next';import { appRouter } from '../../../server/routers/_app';// export API handler// @see https://trpc.io/docs/api-handlerexport default trpcNext.createNextApiHandler({router: appRouter,createContext: () => ({}),});
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
Create a set of strongly-typed hooks using your API's type signature.
utils/trpc.tstsximport { 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 pathreturn '';if (process.env.VERCEL_URL)// reference for vercel.comreturn `https://${process.env.VERCEL_URL}`;if (process.env.RENDER_INTERNAL_HOSTNAME)// reference for render.comreturn `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;// assume localhostreturn `http://localhost:${process.env.PORT ?? 3000}`;}export const trpc = createTRPCNext<AppRouter>({config({ ctx }) {return {links: [httpBatchLink({/*** If you want to use SSR, you need to use the server's full URL* @link https://trpc.io/docs/ssr**/url: `${getBaseUrl()}/api/trpc`,}),],/*** @link https://tanstack.com/query/v4/docs/reference/QueryClient**/// queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },};},/*** @link https://trpc.io/docs/ssr**/ssr: false,});
utils/trpc.tstsximport { 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 pathreturn '';if (process.env.VERCEL_URL)// reference for vercel.comreturn `https://${process.env.VERCEL_URL}`;if (process.env.RENDER_INTERNAL_HOSTNAME)// reference for render.comreturn `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;// assume localhostreturn `http://localhost:${process.env.PORT ?? 3000}`;}export const trpc = createTRPCNext<AppRouter>({config({ ctx }) {return {links: [httpBatchLink({/*** If you want to use SSR, you need to use the server's full URL* @link https://trpc.io/docs/ssr**/url: `${getBaseUrl()}/api/trpc`,}),],/*** @link https://tanstack.com/query/v4/docs/reference/QueryClient**/// queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },};},/*** @link https://trpc.io/docs/ssr**/ssr: false,});
createTRPCNext does not work with interop mode. If you are migrating from v9 using interop, keep using the old way of initializing tRPC.
5. Configure _app.tsx
pages/_app.tsxtsximport type { AppType } from 'next/app';import { trpc } from '../utils/trpc';const MyApp: AppType = ({ Component, pageProps }) => {return <Component {...pageProps} />;};export default trpc.withTRPC(MyApp);
pages/_app.tsxtsximport 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
pages/index.tsxtsximport { 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>);}
pages/index.tsxtsximport { 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 has a ctx input that gives you access to the Next.js req object, among other things. The returned value can contain the following properties:
- Required:
linksto customize the flow of data between tRPC Client and the tRPC Server. Read more.- Optional:
queryClientConfig: a configuration object for the React QueryQueryClientused internally by the tRPC React hooks: QueryClient docsqueryClient: a React Query QueryClient instance- Note: You can only provide either a
queryClientor aqueryClientConfig.
- Note: You can only provide either a
transformer: a transformer applied to outgoing payloads. Read more about Data TransformersabortOnUnmount: determines if in-flight requests will be cancelled on component unmount. This defaults tofalse.
unstable_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.
responseMeta-callback
Ability to set request headers and HTTP status when server-side rendering.
Example
utils/trpc.tstsximport { createTRPCNext } from '@trpc/next';import type { AppRouter } from '../pages/api/trpc/[trpc]';export const trpc = createTRPCNext<AppRouter>({config({ ctx }) {/* [...] */},ssr: true,responseMeta({ clientErrors, ctx }) {if (clientErrors.length) {// propagate first http error from API callsreturn {status: clientErrors[0].data?.httpStatus ?? 500,};}// cache full page for 1 day + revalidate once every secondconst ONE_DAY_IN_SECONDS = 60 * 60 * 24;return {'Cache-Control': `s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,};},});
utils/trpc.tstsximport { createTRPCNext } from '@trpc/next';import type { AppRouter } from '../pages/api/trpc/[trpc]';export const trpc = createTRPCNext<AppRouter>({config({ ctx }) {/* [...] */},ssr: true,responseMeta({ clientErrors, ctx }) {if (clientErrors.length) {// propagate first http error from API callsreturn {status: clientErrors[0].data?.httpStatus ?? 500,};}// cache full page for 1 day + revalidate once every secondconst ONE_DAY_IN_SECONDS = 60 * 60 * 24;return {'Cache-Control': `s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,};},});
Next steps
Browse the rest of the docs to learn more about things like authorization, middlewares, and error handling.
You can also find usual information about queries and mutations now that you're using @trpc/react-query.