Konfigurera med Next.js App Router
Denna sida har översatts av PageTurner AI (beta). Inte officiellt godkänd av projektet. Hittade du ett fel? Rapportera problem →
Vi rekommenderar att läsa TanStack React Querys dokumentation om Avancerad serverrendering för att förstå olika typer av serverrendering och fallgropar att undvika.
Rekommenderad filstruktur
graphql.├── src│ ├── app│ │ ├── api│ │ │ └── trpc│ │ │ └── [trpc]│ │ │ └── route.ts # <-- tRPC HTTP handler│ │ ├── layout.tsx # <-- mount TRPCReactProvider│ │ └── page.tsx # <-- server component│ ├── trpc│ │ ├── init.ts # <-- tRPC server init & context│ │ ├── routers│ │ │ ├── _app.ts # <-- main app router│ │ │ ├── post.ts # <-- sub routers│ │ │ └── [..]│ │ ├── client.tsx # <-- client hooks & provider│ │ ├── query-client.ts # <-- shared QueryClient factory│ │ └── server.tsx # <-- server-side caller│ └── [..]└── [..]
graphql.├── src│ ├── app│ │ ├── api│ │ │ └── trpc│ │ │ └── [trpc]│ │ │ └── route.ts # <-- tRPC HTTP handler│ │ ├── layout.tsx # <-- mount TRPCReactProvider│ │ └── page.tsx # <-- server component│ ├── trpc│ │ ├── init.ts # <-- tRPC server init & context│ │ ├── routers│ │ │ ├── _app.ts # <-- main app router│ │ │ ├── post.ts # <-- sub routers│ │ │ └── [..]│ │ ├── client.tsx # <-- client hooks & provider│ │ ├── query-client.ts # <-- shared QueryClient factory│ │ └── server.tsx # <-- server-side caller│ └── [..]└── [..]
Lägg till tRPC i ett befintligt Next.js App Router-projekt
1. Installera beroenden
- npm
- yarn
- pnpm
- bun
- deno
npm install @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query@latest zod client-only server-only
yarn add @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query@latest zod client-only server-only
pnpm add @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query@latest zod client-only server-only
bun add @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query@latest zod client-only server-only
deno add npm:@trpc/server npm:@trpc/client npm:@trpc/tanstack-react-query npm:@tanstack/react-query@latest npm:zod npm:client-only npm:server-only
Om du använder en AI-kodningsagent, installera tRPC-färdigheter för bättre kodgenerering:
bashnpx @tanstack/intent@latest install
bashnpx @tanstack/intent@latest install
2. Skapa en tRPC-router
Initiera din tRPC-backend i trpc/init.ts med funktionen initTRPC och skapa din första router. Här skapar vi en enkel "hello world"-router och procedur - för mer djupgående information om att skapa ditt tRPC-API, se Snabbstartsguiden och Dokumentation för backend-användning.
trpc/init.tstsimport {initTRPC } from '@trpc/server';/*** This context creator accepts `headers` so it can be reused in both* the RSC server caller (where you pass `next/headers`) and the* API route handler (where you pass the request headers).*/export constcreateTRPCContext = async (opts : {headers :Headers }) => {// const user = await auth(opts.headers);return {userId : 'user_123' };};// 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.constt =initTRPC .context <Awaited <ReturnType <typeofcreateTRPCContext >>>().create ({/*** @see https://trpc.io/docs/server/data-transformers*/// transformer: superjson,});// Base router and procedure helpersexport constcreateTRPCRouter =t .router ;export constcreateCallerFactory =t .createCallerFactory ;export constbaseProcedure =t .procedure ;
trpc/init.tstsimport {initTRPC } from '@trpc/server';/*** This context creator accepts `headers` so it can be reused in both* the RSC server caller (where you pass `next/headers`) and the* API route handler (where you pass the request headers).*/export constcreateTRPCContext = async (opts : {headers :Headers }) => {// const user = await auth(opts.headers);return {userId : 'user_123' };};// 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.constt =initTRPC .context <Awaited <ReturnType <typeofcreateTRPCContext >>>().create ({/*** @see https://trpc.io/docs/server/data-transformers*/// transformer: superjson,});// Base router and procedure helpersexport constcreateTRPCRouter =t .router ;export constcreateCallerFactory =t .createCallerFactory ;export constbaseProcedure =t .procedure ;
trpc/routers/_app.tstsimport {z } from 'zod';import {baseProcedure ,createTRPCRouter } from '../init';export constappRouter =createTRPCRouter ({hello :baseProcedure .input (z .object ({text :z .string (),}),).query ((opts ) => {return {greeting : `hello ${opts .input .text }`,};}),});// export type definition of APIexport typeAppRouter = typeofappRouter ;
trpc/routers/_app.tstsimport {z } from 'zod';import {baseProcedure ,createTRPCRouter } from '../init';export constappRouter =createTRPCRouter ({hello :baseProcedure .input (z .object ({text :z .string (),}),).query ((opts ) => {return {greeting : `hello ${opts .input .text }`,};}),});// export type definition of APIexport typeAppRouter = typeofappRouter ;
3. Skapa API route handler
Med App Router använder du fetch-adaptern för att hantera tRPC-förfrågningar. Skapa en route handler som exporterar både GET och POST:
app/api/trpc/[trpc]/route.tstsimport {fetchRequestHandler } from '@trpc/server/adapters/fetch';import {createTRPCContext } from './trpc/init';import {appRouter } from './trpc/routers/_app';consthandler = (req :Request ) =>fetchRequestHandler ({endpoint : '/api/trpc',req ,router :appRouter ,createContext : () =>createTRPCContext ({headers :req .headers }),});export {handler asGET ,handler asPOST };
app/api/trpc/[trpc]/route.tstsimport {fetchRequestHandler } from '@trpc/server/adapters/fetch';import {createTRPCContext } from './trpc/init';import {appRouter } from './trpc/routers/_app';consthandler = (req :Request ) =>fetchRequestHandler ({endpoint : '/api/trpc',req ,router :appRouter ,createContext : () =>createTRPCContext ({headers :req .headers }),});export {handler asGET ,handler asPOST };
App Router använder fetch-adaptern (via fetchRequestHandler) istället för den Next.js-specifika adapter som används av Pages Router. Detta beror på att App Router route handlers bygger på standardwebbobjekten Request och Response.
4. Skapa en Query Client-fabrik
Skapa en delad fil trpc/query-client.ts som exporterar en funktion som skapar en QueryClient-instans.
trpc/query-client.tstsimport {defaultShouldDehydrateQuery ,QueryClient ,} from '@tanstack/react-query';importsuperjson from 'superjson';export functionmakeQueryClient () {return newQueryClient ({defaultOptions : {queries : {staleTime : 30 * 1000,},dehydrate : {// serializeData: superjson.serialize,shouldDehydrateQuery : (query ) =>defaultShouldDehydrateQuery (query ) ||query .state .status === 'pending',},hydrate : {// deserializeData: superjson.deserialize,},},});}
trpc/query-client.tstsimport {defaultShouldDehydrateQuery ,QueryClient ,} from '@tanstack/react-query';importsuperjson from 'superjson';export functionmakeQueryClient () {return newQueryClient ({defaultOptions : {queries : {staleTime : 30 * 1000,},dehydrate : {// serializeData: superjson.serialize,shouldDehydrateQuery : (query ) =>defaultShouldDehydrateQuery (query ) ||query .state .status === 'pending',},hydrate : {// deserializeData: superjson.deserialize,},},});}
Vi sätter några standardalternativ här:
-
staleTime: Med SSR vill vi vanligtvis sätta en standard staleTime över 0 för att undvika omedelbar hämtning på klienten. -
shouldDehydrateQuery: Det här är en funktion som avgör om en fråga ska dehydreras eller inte. Eftersom RSC-transportprotokollet stöder hydrering av löften över nätverket utökar vi funktionendefaultShouldDehydrateQuerytill att även inkludera frågor som fortfarande väntar. Detta låter oss börja med förhämtning i en serverkomponent högt upp i trädet och sedan konsumera det löftet i en klientkomponent längre ner. -
serializeDataochdeserializeData(valfritt): Om du konfigurerade en datatransformer i föregående steg, sätt det här alternativet för att säkerställa att data serialiseras korrekt när query client hydreras över server-klient-gränsen.
5. Skapa en tRPC-klient för klientkomponenter
trpc/client.tsx är ingångspunkten när du använder ditt tRPC-API från klientkomponenter. Importera här typdefinitionen för
din tRPC-router och skapa typsäkra hooks med createTRPCContext. Vi exporterar också vår context-provider från den här filen.
trpc/client.tsxtsx'use client';// ^-- to make sure we can mount the Provider from a server componentimport type {QueryClient } from '@tanstack/react-query';import {QueryClientProvider } from '@tanstack/react-query';import {createTRPCClient ,httpBatchLink } from '@trpc/client';import {createTRPCContext } from '@trpc/tanstack-react-query';import {useState } from 'react';import {makeQueryClient } from './query-client';import type {AppRouter } from './routers/_app';export const {TRPCProvider ,useTRPC } =createTRPCContext <AppRouter >();letbrowserQueryClient :QueryClient ;functiongetQueryClient () {if (typeofwindow === 'undefined') {// Server: always make a new query clientreturnmakeQueryClient ();}// Browser: make a new query client if we don't already have one// This is very important, so we don't re-make a new client if React// suspends during the initial render. This may not be needed if we// have a suspense boundary BELOW the creation of the query clientif (!browserQueryClient )browserQueryClient =makeQueryClient ();returnbrowserQueryClient ;}functiongetUrl () {constbase = (() => {if (typeofwindow !== 'undefined') return '';if (process .env .VERCEL_URL ) return `https://${process .env .VERCEL_URL }`;return 'http://localhost:3000';})();return `${base }/api/trpc`;}export functionTRPCReactProvider (props :Readonly <{children :React .ReactNode ;}>,) {// NOTE: Avoid useState when initializing the query client if you don't// have a suspense boundary between this and the code that may// suspend because React will throw away the client on the initial// render if it suspends and there is no boundaryconstqueryClient =getQueryClient ();const [trpcClient ] =useState (() =>createTRPCClient <AppRouter >({links : [httpBatchLink ({// transformer: superjson, <-- if you use a data transformerurl :getUrl (),}),],}),);return (<QueryClientProvider client ={queryClient }><TRPCProvider trpcClient ={trpcClient }queryClient ={queryClient }>{props .children }</TRPCProvider ></QueryClientProvider >);}
trpc/client.tsxtsx'use client';// ^-- to make sure we can mount the Provider from a server componentimport type {QueryClient } from '@tanstack/react-query';import {QueryClientProvider } from '@tanstack/react-query';import {createTRPCClient ,httpBatchLink } from '@trpc/client';import {createTRPCContext } from '@trpc/tanstack-react-query';import {useState } from 'react';import {makeQueryClient } from './query-client';import type {AppRouter } from './routers/_app';export const {TRPCProvider ,useTRPC } =createTRPCContext <AppRouter >();letbrowserQueryClient :QueryClient ;functiongetQueryClient () {if (typeofwindow === 'undefined') {// Server: always make a new query clientreturnmakeQueryClient ();}// Browser: make a new query client if we don't already have one// This is very important, so we don't re-make a new client if React// suspends during the initial render. This may not be needed if we// have a suspense boundary BELOW the creation of the query clientif (!browserQueryClient )browserQueryClient =makeQueryClient ();returnbrowserQueryClient ;}functiongetUrl () {constbase = (() => {if (typeofwindow !== 'undefined') return '';if (process .env .VERCEL_URL ) return `https://${process .env .VERCEL_URL }`;return 'http://localhost:3000';})();return `${base }/api/trpc`;}export functionTRPCReactProvider (props :Readonly <{children :React .ReactNode ;}>,) {// NOTE: Avoid useState when initializing the query client if you don't// have a suspense boundary between this and the code that may// suspend because React will throw away the client on the initial// render if it suspends and there is no boundaryconstqueryClient =getQueryClient ();const [trpcClient ] =useState (() =>createTRPCClient <AppRouter >({links : [httpBatchLink ({// transformer: superjson, <-- if you use a data transformerurl :getUrl (),}),],}),);return (<QueryClientProvider client ={queryClient }><TRPCProvider trpcClient ={trpcClient }queryClient ={queryClient }>{props .children }</TRPCProvider ></QueryClientProvider >);}
Montera providern i root-layouten för din applikation:
app/layout.tsxtsximport {TRPCReactProvider } from '~/trpc/client';export default functionRootLayout ({children ,}:Readonly <{children :React .ReactNode ;}>) {return (<html lang ="en"><body ><TRPCReactProvider >{children }</TRPCReactProvider ></body ></html >);}
app/layout.tsxtsximport {TRPCReactProvider } from '~/trpc/client';export default functionRootLayout ({children ,}:Readonly <{children :React .ReactNode ;}>) {return (<html lang ="en"><body ><TRPCReactProvider >{children }</TRPCReactProvider ></body ></html >);}
6. Skapa en tRPC-anropare för serverkomponenter
För att förhämta frågor från serverkomponenter skapar vi en proxy från vår router. Du kan också skicka med en klient om din router finns på en separat server.
trpc/server.tsxtsximport 'server-only'; // <-- ensure this file cannot be imported from the clientimport {createTRPCOptionsProxy } from '@trpc/tanstack-react-query';import {headers } from 'next/headers';import {cache } from 'react';import {createTRPCContext } from './init';import {makeQueryClient } from './query-client';import {appRouter } from './routers/_app';// IMPORTANT: Create a stable getter for the query client that// will return the same client during the same request.export constgetQueryClient =cache (makeQueryClient );export consttrpc =createTRPCOptionsProxy ({ctx : async () =>createTRPCContext ({headers : awaitheaders (),}),router :appRouter ,queryClient :getQueryClient ,});// If your router is on a separate server, pass a client instead:// createTRPCOptionsProxy({// client: createTRPCClient({ links: [httpLink({ url: '...' })] }),// queryClient: getQueryClient,// });
trpc/server.tsxtsximport 'server-only'; // <-- ensure this file cannot be imported from the clientimport {createTRPCOptionsProxy } from '@trpc/tanstack-react-query';import {headers } from 'next/headers';import {cache } from 'react';import {createTRPCContext } from './init';import {makeQueryClient } from './query-client';import {appRouter } from './routers/_app';// IMPORTANT: Create a stable getter for the query client that// will return the same client during the same request.export constgetQueryClient =cache (makeQueryClient );export consttrpc =createTRPCOptionsProxy ({ctx : async () =>createTRPCContext ({headers : awaitheaders (),}),router :appRouter ,queryClient :getQueryClient ,});// If your router is on a separate server, pass a client instead:// createTRPCOptionsProxy({// client: createTRPCClient({ links: [httpLink({ url: '...' })] }),// queryClient: getQueryClient,// });
7. Gör API-anrop
Allt är klart! Du kan nu förhämta queries i serverkomponenter och använda dem i klientkomponenter.
Förhämtning i en serverkomponent
app/page.tsxtsximport {dehydrate ,HydrationBoundary } from '@tanstack/react-query';import {getQueryClient ,trpc } from '~/trpc/server';import {ClientGreeting } from './client-greeting';export default async functionHome () {constqueryClient =getQueryClient ();voidqueryClient .prefetchQuery (trpc .hello .queryOptions ({text : 'world',}),);return (<HydrationBoundary state ={dehydrate (queryClient )}><ClientGreeting /></HydrationBoundary >);}
app/page.tsxtsximport {dehydrate ,HydrationBoundary } from '@tanstack/react-query';import {getQueryClient ,trpc } from '~/trpc/server';import {ClientGreeting } from './client-greeting';export default async functionHome () {constqueryClient =getQueryClient ();voidqueryClient .prefetchQuery (trpc .hello .queryOptions ({text : 'world',}),);return (<HydrationBoundary state ={dehydrate (queryClient )}><ClientGreeting /></HydrationBoundary >);}
Använda data i en klientkomponent
app/client-greeting.tsxtsx'use client';// <-- hooks can only be used in client componentsimport {useQuery } from '@tanstack/react-query';import {useTRPC } from '~/trpc/client';export functionClientGreeting () {consttrpc =useTRPC ();constgreeting =useQuery (trpc .hello .queryOptions ({text : 'world' }));if (!greeting .data ) return <div >Loading...</div >;return <div >{greeting .data .greeting }</div >;}
app/client-greeting.tsxtsx'use client';// <-- hooks can only be used in client componentsimport {useQuery } from '@tanstack/react-query';import {useTRPC } from '~/trpc/client';export functionClientGreeting () {consttrpc =useTRPC ();constgreeting =useQuery (trpc .hello .queryOptions ({text : 'world' }));if (!greeting .data ) return <div >Loading...</div >;return <div >{greeting .data .greeting }</div >;}
Du kan skapa hjälpfunktionerna prefetch och HydrateClient för att göra detta mer koncist:
trpc/server.tsxtsxexport functionHydrateClient (props : {children :React .ReactNode }) {constqueryClient =getQueryClient ();return (<HydrationBoundary state ={dehydrate (queryClient )}>{props .children }</HydrationBoundary >);}export functionprefetch <T extendsReturnType <TRPCQueryOptions <any>>>(queryOptions :T ,) {constqueryClient =getQueryClient ();if (queryOptions .queryKey [1]?.type === 'infinite') {voidqueryClient .prefetchInfiniteQuery (queryOptions as any);} else {voidqueryClient .prefetchQuery (queryOptions );}}
trpc/server.tsxtsxexport functionHydrateClient (props : {children :React .ReactNode }) {constqueryClient =getQueryClient ();return (<HydrationBoundary state ={dehydrate (queryClient )}>{props .children }</HydrationBoundary >);}export functionprefetch <T extendsReturnType <TRPCQueryOptions <any>>>(queryOptions :T ,) {constqueryClient =getQueryClient ();if (queryOptions .queryKey [1]?.type === 'infinite') {voidqueryClient .prefetchInfiniteQuery (queryOptions as any);} else {voidqueryClient .prefetchQuery (queryOptions );}}
Sedan kan du använda det så här:
tsximport {HydrateClient ,prefetch ,trpc } from '~/trpc/server';import {ClientGreeting } from './client-greeting';functionHome () {prefetch (trpc .hello .queryOptions ({text : 'world' }));return (<HydrateClient ><ClientGreeting /></HydrateClient >);}
tsximport {HydrateClient ,prefetch ,trpc } from '~/trpc/server';import {ClientGreeting } from './client-greeting';functionHome () {prefetch (trpc .hello .queryOptions ({text : 'world' }));return (<HydrateClient ><ClientGreeting /></HydrateClient >);}
Utnyttja Suspense
Du kan föredra att hantera laddnings- och felstatusar med Suspense och Error Boundaries. Det gör du med useSuspenseQuery-hooken.
app/page.tsxtsximport {HydrateClient ,prefetch ,trpc } from '~/trpc/server';import {Suspense } from 'react';import {ErrorBoundary } from 'react-error-boundary';import {ClientGreeting } from './client-greeting';export default async functionHome () {prefetch (trpc .hello .queryOptions ());return (<HydrateClient ><ErrorBoundary fallback ={<div >Something went wrong</div >}><Suspense fallback ={<div >Loading...</div >}><ClientGreeting /></Suspense ></ErrorBoundary ></HydrateClient >);}
app/page.tsxtsximport {HydrateClient ,prefetch ,trpc } from '~/trpc/server';import {Suspense } from 'react';import {ErrorBoundary } from 'react-error-boundary';import {ClientGreeting } from './client-greeting';export default async functionHome () {prefetch (trpc .hello .queryOptions ());return (<HydrateClient ><ErrorBoundary fallback ={<div >Something went wrong</div >}><Suspense fallback ={<div >Loading...</div >}><ClientGreeting /></Suspense ></ErrorBoundary ></HydrateClient >);}
app/client-greeting.tsxtsx'use client';import {useSuspenseQuery } from '@tanstack/react-query';import {useTRPC } from '~/trpc/client';export functionClientGreeting () {consttrpc =useTRPC ();const {data } =useSuspenseQuery (trpc .hello .queryOptions ());return <div >{data .greeting }</div >;}
app/client-greeting.tsxtsx'use client';import {useSuspenseQuery } from '@tanstack/react-query';import {useTRPC } from '~/trpc/client';export functionClientGreeting () {consttrpc =useTRPC ();const {data } =useSuspenseQuery (trpc .hello .queryOptions ());return <div >{data .greeting }</div >;}
Hämta data i en serverkomponent
Om du behöver åtkomst till data i en serverkomponent rekommenderar vi att skapa en servercaller och använda den direkt. Observera att denna metod är frikopplad från din query client och inte lagrar datan i cachen. Det innebär att du inte kan använda datan i en serverkomponent och förvänta dig att den ska finnas tillgänglig i klienten. Detta är avsiktligt och förklaras mer detaljerat i guiden om Avancerad serverrendering.
trpc/server.tsxtsximport {headers } from 'next/headers';import {createTRPCContext } from './init';import {appRouter } from './routers/_app';// ...export constcaller =appRouter .createCaller (async () =>createTRPCContext ({headers : awaitheaders () }),);
trpc/server.tsxtsximport {headers } from 'next/headers';import {createTRPCContext } from './init';import {appRouter } from './routers/_app';// ...export constcaller =appRouter .createCaller (async () =>createTRPCContext ({headers : awaitheaders () }),);
app/page.tsxtsximport {caller } from '~/trpc/server';export default async functionHome () {constgreeting = awaitcaller .hello ();return <div >{greeting .greeting }</div >;}
app/page.tsxtsximport {caller } from '~/trpc/server';export default async functionHome () {constgreeting = awaitcaller .hello ();return <div >{greeting .greeting }</div >;}
Om du verkligen behöver använda datan både på servern och i klientkomponenter och förstår kompromisserna som beskrivs i
guiden Avancerad serverrendering,
kan du använda fetchQuery istället för prefetch för att ha datan både på servern och hydrera den till klienten:
app/page.tsxtsximport {getQueryClient ,HydrateClient ,trpc } from '~/trpc/server';import {ClientGreeting } from './client-greeting';export default async functionHome () {constqueryClient =getQueryClient ();constgreeting = awaitqueryClient .fetchQuery (trpc .hello .queryOptions ());// Do something with greeting on the serverreturn (<HydrateClient ><ClientGreeting /></HydrateClient >);}
app/page.tsxtsximport {getQueryClient ,HydrateClient ,trpc } from '~/trpc/server';import {ClientGreeting } from './client-greeting';export default async functionHome () {constqueryClient =getQueryClient ();constgreeting = awaitqueryClient .fetchQuery (trpc .hello .queryOptions ());// Do something with greeting on the serverreturn (<HydrateClient ><ClientGreeting /></HydrateClient >);}
Nästa steg
-
Läs om Server Actions för att definiera tRPC-drivna serveråtgärder
-
Utforska server-side calls för mer avancerade servermönster
-
Kolla in SSE Chat-exemplet för ett komplett App Router-exempel med prenumerationer