Configuration avec les composants serveur React
Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →
Cette documentation concerne notre intégration "Classique" avec React Query, qui (bien que toujours supportée) n'est pas la méthode recommandée pour démarrer de nouveaux projets tRPC avec TanStack React Query. Nous vous conseillons plutôt d'utiliser la nouvelle Intégration TanStack React Query.
Vous utilisez Next.js ? Consultez le guide de configuration dédié pour App Router pour l'approche recommandée.
Ce guide présente comment utiliser tRPC avec un framework utilisant les composants serveur React (RSC) comme Next.js App Router. Notez que les RSC résolvent déjà de nombreux problèmes que tRPC était conçu pour traiter, donc vous pourriez ne pas avoir besoin de tRPC.
Il n'existe pas non plus de solution universelle pour intégrer tRPC avec les RSC. Considérez ce guide comme un point de départ et adaptez-le à vos besoins et préférences.
Si vous cherchez à utiliser tRPC avec les Server Actions, consultez le guide Server Actions.
Lisez impérativement la documentation Rendu serveur avancé de React Query avant de continuer, pour comprendre les différents types de rendu serveur et les pièges à éviter.
Ajouter tRPC à des projets existants
1. Installer les dépendances
- npm
- yarn
- pnpm
- bun
- deno
npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query@latest zod client-only server-only
yarn add @trpc/server @trpc/client @trpc/react-query @tanstack/react-query@latest zod client-only server-only
pnpm add @trpc/server @trpc/client @trpc/react-query @tanstack/react-query@latest zod client-only server-only
bun add @trpc/server @trpc/client @trpc/react-query @tanstack/react-query@latest zod client-only server-only
deno add npm:@trpc/server npm:@trpc/client npm:@trpc/react-query npm:@tanstack/react-query@latest npm:zod npm:client-only npm:server-only
2. Créer un routeur tRPC
Initialisez votre backend tRPC dans trpc/init.ts avec la fonction initTRPC, puis créez votre premier routeur. Nous allons implémenter ici un routeur et une procédure simples de type "hello world" - mais pour des informations approfondies sur la création de votre API tRPC, reportez-vous au guide de démarrage rapide et à la documentation backend.
Les noms de fichiers utilisés ici ne sont pas imposés par tRPC. Vous pouvez adopter n'importe quelle structure de fichiers.
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. Créer une fabrique de Query Client
Créez un fichier partagé trpc/query-client.ts qui exporte une fonction générant une instance de QueryClient.
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,},},});}
Nous définissons quelques options par défaut :
-
staleTime: Avec le SSR, nous définissons généralement une staleTime par défaut supérieure à 0 pour éviter un rechargement immédiat côté client. -
shouldDehydrateQuery: Cette fonction détermine si une requête doit être déshydratée. Comme le protocole RSC supporte l'hydratation des promesses via le réseau, nous étendons la fonctiondefaultShouldDehydrateQuerypour inclure aussi les requêtes encore en attente. Cela permet de démarrer le préchargement dans un composant serveur haut dans l'arbre, puis de consommer cette promesse dans un composant client plus bas. -
serializeDataetdeserializeData(optionnel) : Si vous avez configuré un transformateur de données à l'étape précédente, définissez cette option pour garantir une sérialisation correcte lors de l'hydratation du Query Client à travers la frontière serveur-client.
4. Créer un client tRPC pour les composants clients
Le fichier trpc/client.tsx est le point d'entrée pour consommer votre API tRPC depuis les composants clients. Importez-y la définition de type
de votre routeur tRPC et créez des hooks typés avec createTRPCReact. Nous exporterons également notre fournisseur de contexte depuis ce fichier.
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 {httpBatchLink } from '@trpc/client';import {createTRPCReact } from '@trpc/react-query';importReact , {useState } from 'react';import {makeQueryClient } from './query-client';import type {AppRouter } from './routers/_app';export consttrpc =createTRPCReact <AppRouter >();letclientQueryClientSingleton :QueryClient ;functiongetQueryClient () {if (typeofwindow === 'undefined') {// Server: always make a new query clientreturnmakeQueryClient ();}// Browser: use singleton pattern to keep the same query clientreturn (clientQueryClientSingleton ??=makeQueryClient ());}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 functionTRPCProvider (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 (() =>trpc .createClient ({links : [httpBatchLink ({// transformer: superjson, <-- if you use a data transformerurl :getUrl (),}),],}),);return (<trpc .Provider client ={trpcClient }queryClient ={queryClient }><QueryClientProvider client ={queryClient }>{props .children }</QueryClientProvider ></trpc .Provider >);}
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 {httpBatchLink } from '@trpc/client';import {createTRPCReact } from '@trpc/react-query';importReact , {useState } from 'react';import {makeQueryClient } from './query-client';import type {AppRouter } from './routers/_app';export consttrpc =createTRPCReact <AppRouter >();letclientQueryClientSingleton :QueryClient ;functiongetQueryClient () {if (typeofwindow === 'undefined') {// Server: always make a new query clientreturnmakeQueryClient ();}// Browser: use singleton pattern to keep the same query clientreturn (clientQueryClientSingleton ??=makeQueryClient ());}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 functionTRPCProvider (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 (() =>trpc .createClient ({links : [httpBatchLink ({// transformer: superjson, <-- if you use a data transformerurl :getUrl (),}),],}),);return (<trpc .Provider client ={trpcClient }queryClient ={queryClient }><QueryClientProvider client ={queryClient }>{props .children }</QueryClientProvider ></trpc .Provider >);}
Montez le fournisseur à la racine de votre application (par exemple dans app/layout.tsx avec Next.js).
5. Créer un appelant tRPC pour les composants serveur
Pour précharger les requêtes depuis les composants serveur, nous utilisons un appelant tRPC. Le module @trpc/react-query/rsc exporte une fine surcouche autour de
createCaller qui s'intègre avec votre client React Query.
trpc/server.tsxtsximport 'server-only'; // <-- ensure this file cannot be imported from the clientimport {createHydrationHelpers } from '@trpc/react-query/rsc';import {cache } from 'react';import {createCallerFactory ,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 );constcaller =createCallerFactory (appRouter )(createTRPCContext );export const {trpc ,HydrateClient } =createHydrationHelpers <typeofappRouter >(caller ,getQueryClient ,);
trpc/server.tsxtsximport 'server-only'; // <-- ensure this file cannot be imported from the clientimport {createHydrationHelpers } from '@trpc/react-query/rsc';import {cache } from 'react';import {createCallerFactory ,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 );constcaller =createCallerFactory (appRouter )(createTRPCContext );export const {trpc ,HydrateClient } =createHydrationHelpers <typeofappRouter >(caller ,getQueryClient ,);
Utiliser votre API
Vous pouvez désormais utiliser votre API tRPC dans votre application. Si les hooks React Query fonctionnent dans les composants clients comme dans toute application React classique, nous pouvons exploiter les capacités RSC en préchargeant les requêtes dans un composant serveur haut dans l'arbre. Vous connaissez peut-être ce concept sous le nom de "render as you fetch" (rendu pendant le chargement), souvent implémenté via des loaders. Cela signifie que la requête est déclenchée le plus tôt possible sans suspension jusqu'à ce que les données soient nécessaires, en utilisant les hooks useQuery ou useSuspenseQuery.
app/page.tsxtsximport {trpc ,HydrateClient } from '../trpc/server';import {ClientGreeting } from './client-greeting';export default async functionHome () {voidtrpc .hello .prefetch ();return (<HydrateClient ><div >...</div >{/** ... */}<ClientGreeting /></HydrateClient >);}
app/page.tsxtsximport {trpc ,HydrateClient } from '../trpc/server';import {ClientGreeting } from './client-greeting';export default async functionHome () {voidtrpc .hello .prefetch ();return (<HydrateClient ><div >...</div >{/** ... */}<ClientGreeting /></HydrateClient >);}
app/client-greeting.tsxtsx'use client';// <-- hooks can only be used in client componentsimport {trpc } from '../trpc/client';export functionClientGreeting () {constgreeting =trpc .hello .useQuery ();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 {trpc } from '../trpc/client';export functionClientGreeting () {constgreeting =trpc .hello .useQuery ();if (!greeting .data ) return <div >Loading...</div >;return <div >{greeting .data .greeting }</div >;}
Exploiter Suspense
Vous pouvez préférer gérer les états de chargement et d'erreur via Suspense et les Error Boundaries en utilisant le hook useSuspenseQuery.
app/page.tsxtsximport {trpc ,HydrateClient } from '../trpc/server';import {Suspense } from 'react';import {ErrorBoundary } from 'react-error-boundary';import {ClientGreeting } from './client-greeting';export default async functionHome () {voidtrpc .hello .prefetch ();return (<HydrateClient ><div >...</div >{/** ... */}<ErrorBoundary fallback ={<div >Something went wrong</div >}><Suspense fallback ={<div >Loading...</div >}><ClientGreeting /></Suspense ></ErrorBoundary ></HydrateClient >);}
app/page.tsxtsximport {trpc ,HydrateClient } from '../trpc/server';import {Suspense } from 'react';import {ErrorBoundary } from 'react-error-boundary';import {ClientGreeting } from './client-greeting';export default async functionHome () {voidtrpc .hello .prefetch ();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 {trpc } from '../trpc/client';export functionClientGreeting () {const [data ] =trpc .hello .useSuspenseQuery ();return <div >{data .greeting }</div >;}
app/client-greeting.tsxtsx'use client';import {trpc } from '../trpc/client';export functionClientGreeting () {const [data ] =trpc .hello .useSuspenseQuery ();return <div >{data .greeting }</div >;}
Récupérer des données dans un composant serveur
Si vous devez accéder aux données dans un composant serveur, vous pouvez invoquer la procédure directement plutôt qu'utiliser .prefetch(), comme avec l'appelant serveur standard. Notez que cette méthode est détachée de votre Query Client et ne stocke pas les données dans le cache. Vous ne pouvez donc pas utiliser les données dans un composant serveur et les attendre disponibles côté client. Ce comportement est intentionnel et expliqué en détail dans le guide Rendu serveur avancé.
app/page.tsxtsximport {trpc } from '../trpc/server';export default async functionHome () {// Use the caller directly without using `.prefetch()`constgreeting = awaittrpc .hello ();// ^? { greeting: string }return <div >{greeting .greeting }</div >;}
app/page.tsxtsximport {trpc } from '../trpc/server';export default async functionHome () {// Use the caller directly without using `.prefetch()`constgreeting = awaittrpc .hello ();// ^? { greeting: string }return <div >{greeting .greeting }</div >;}