Konfigurera med React Server Components
Denna sida har översatts av PageTurner AI (beta). Inte officiellt godkänd av projektet. Hittade du ett fel? Rapportera problem →
Använder du Next.js? Se den dedikerade guiden för att konfigurera Next.js App Router för en strömlinjeformad genomgång anpassad för Next.js.
Den här guiden ger en översikt över hur man kan använda tRPC med ett React Server Components (RSC)-ramverk som Next.js App Router. Observera att RSC i sig löser många av samma problem som tRPC designades för, så du kanske inte behöver tRPC alls.
Det finns inte heller något universellt sätt att integrera tRPC med RSC, så se den här guiden som en startpunkt och anpassa den efter dina behov och preferenser.
Om du vill veta hur du använder tRPC med Server Actions, se guiden för Server Actions.
Läs React Querys dokumentation om avancerad serverrendering innan du fortsätter för att förstå olika typer av serverrendering och vilka fallgropar du bör undvika.
Lägg till tRPC till befintliga 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
2. Skapa en tRPC-router
Initiera din tRPC-backend i trpc/init.ts med funktionen initTRPC och skapa din första router. Vi skapar en enkel "hello world"-router och procedur här - men för djupare information om att skapa ditt tRPC-API bör du läsa Snabbstartsguiden och dokumentationen för backend-användning för tRPC-information.
Filnamnen som används här är inte tvingande för tRPC. Du kan använda vilken filstruktur du vill.
View sample backend
trpc/init.tstsimport {initTRPC } from '@trpc/server';import {cache } from 'react';export constcreateTRPCContext =cache (async () => {/*** @see: https://trpc.io/docs/server/context*/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 .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';import {cache } from 'react';export constcreateTRPCContext =cache (async () => {/*** @see: https://trpc.io/docs/server/context*/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 .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 ;
The backend adapter depends on your framework and how it sets up API routes. The following example sets up GET and POST routes at /api/trpc/* using the fetch adapter in Next.js.
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 ,});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 ,});export {handler asGET ,handler asPOST };
3. 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.
4. 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 roten av din applikation (t.ex. app/layout.tsx när du använder Next.js).
5. Skapa en tRPC-caller 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 {createTRPCClient ,httpLink } from '@trpc/client';import {cache } from 'react';import {createTRPCContext } from './init';import {makeQueryClient } from './query-client';import {appRouter } from './routers/_app';import type {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 :createTRPCContext ,router :appRouter ,queryClient :getQueryClient ,});// If your router is on a separate server, pass a client:createTRPCOptionsProxy <AppRouter >({client :createTRPCClient <AppRouter >({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 {createTRPCClient ,httpLink } from '@trpc/client';import {cache } from 'react';import {createTRPCContext } from './init';import {makeQueryClient } from './query-client';import {appRouter } from './routers/_app';import type {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 :createTRPCContext ,router :appRouter ,queryClient :getQueryClient ,});// If your router is on a separate server, pass a client:createTRPCOptionsProxy <AppRouter >({client :createTRPCClient <AppRouter >({links : [httpLink ({url : '...' })],}),queryClient :getQueryClient ,});
Använda ditt API
Nu kan du använda ditt tRPC-API i din applikation. Även om du kan använda React Query-hooks i klientkomponenter precis som i andra React-appar,
kan vi dra nytta av RSC-funktioner genom att förhämta frågor i en serverkomponent högt upp i trädet. Du kanske känner igen detta
koncept som "render as you fetch", vanligtvis implementerat som loaders. Det innebär att begäran startar så snart som möjligt men utan att blockera förrän
data behövs genom att använda useQuery eller useSuspenseQuery-hooks.
Denna metod utnyttjar Next.js App Routers strömningsfunktioner, initierar frågan på servern och strömmar data till klienten när den blir tillgänglig.
Den optimerar både tiden till första byte i webbläsaren och hämtningstiden för data, vilket resulterar i snabbare sidladdningar.
Observera dock att greeting.data initialt kan vara undefined innan datan strömmar in.
Om du föredrar att undvika detta initiala tillstånd med undefined kan du awaita anropet till prefetchQuery.
Det säkerställer att frågan på klienten alltid har data vid första rendering, men det medför en avvägning -
sidan kommer att ladda långsammare eftersom servern måste slutföra frågan innan HTML skickas till klienten.
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 ({/** input */}),);return (<HydrationBoundary state ={dehydrate (queryClient )}><div >...</div >{/** ... */}<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 ({/** input */}),);return (<HydrationBoundary state ={dehydrate (queryClient )}><div >...</div >{/** ... */}<ClientGreeting /></HydrationBoundary >);}
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 också skapa hjälpfunktioner prefetch och HydrateClient för att göra koden mer koncis och återanvändbar:
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 dem så här:
tsximport {HydrateClient ,prefetch ,trpc } from '../trpc/server';import {ClientGreeting } from './client-greeting';functionHome () {prefetch (trpc .hello .queryOptions ({/** input */}),);return (<HydrateClient ><div >...</div >{/** ... */}<ClientGreeting /></HydrateClient >);}
tsximport {HydrateClient ,prefetch ,trpc } from '../trpc/server';import {ClientGreeting } from './client-greeting';functionHome () {prefetch (trpc .hello .queryOptions ({/** input */}),);return (<HydrateClient ><div >...</div >{/** ... */}<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 ><div >...</div >{/** ... */}<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 ><div >...</div >{/** ... */}<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.tsxtsx// ...export constcaller =appRouter .createCaller (createTRPCContext );
trpc/server.tsxtsx// ...export constcaller =appRouter .createCaller (createTRPCContext );
app/page.tsxtsximport {caller } from '../trpc/server';export default async functionHome () {constgreeting = awaitcaller .hello ();// ^? { greeting: string }return <div >{greeting .greeting }</div >;}
app/page.tsxtsximport {caller } from '../trpc/server';export default async functionHome () {constgreeting = awaitcaller .hello ();// ^? { greeting: string }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 ><div >...</div >{/** ... */}<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 ><div >...</div >{/** ... */}<ClientGreeting /></HydrateClient >);}