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 →
Det här är dokumentationen för vår 'klassiska' React Query-integration, som (fortfarande stöds) inte är det rekommenderade sättet att starta nya tRPC-projekt med TanStack React Query. Vi rekommenderar istället den nya TanStack React Query-integrationen.
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 heller inte en universallösning för att integrera tRPC med RSC, så se den här guiden som en utgångspunkt och anpassa den efter dina behov och preferenser.
Om du letar efter hur du använder tRPC med Server Actions, kolla in det här blogginlägget av Julius.
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/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. 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 - men för djupgående information om hur du skapar ditt tRPC-API bör du hänvisa till Snabbstartsguiden och Backend-dokumentationen 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 const createTRPCContext = 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.const t = initTRPC.create({/*** @see https://trpc.io/docs/server/data-transformers*/// transformer: superjson,});// Base router and procedure helpersexport const createTRPCRouter = t.router;export const createCallerFactory = t.createCallerFactory;export const baseProcedure = t.procedure;
trpc/init.tstsimport { initTRPC } from '@trpc/server';import { cache } from 'react';export const createTRPCContext = 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.const t = initTRPC.create({/*** @see https://trpc.io/docs/server/data-transformers*/// transformer: superjson,});// Base router and procedure helpersexport const createTRPCRouter = t.router;export const createCallerFactory = t.createCallerFactory;export const baseProcedure = t.procedure;
trpc/routers/_app.tstsimport { z } from 'zod';import { baseProcedure, createTRPCRouter } from '../init';export const appRouter = createTRPCRouter({hello: baseProcedure.input(z.object({text: z.string(),}),).query((opts) => {return {greeting: `hello ${opts.input.text}`,};}),});// export type definition of APIexport type AppRouter = typeof appRouter;
trpc/routers/_app.tstsimport { z } from 'zod';import { baseProcedure, createTRPCRouter } from '../init';export const appRouter = createTRPCRouter({hello: baseProcedure.input(z.object({text: z.string(),}),).query((opts) => {return {greeting: `hello ${opts.input.text}`,};}),});// export type definition of APIexport type AppRouter = typeof appRouter;
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';const handler = (req: Request) =>fetchRequestHandler({endpoint: '/api/trpc',req,router: appRouter,createContext: createTRPCContext,});export { handler as GET, handler as POST };
app/api/trpc/[trpc]/route.tstsimport { fetchRequestHandler } from '@trpc/server/adapters/fetch';import { createTRPCContext } from '~/trpc/init';import { appRouter } from '~/trpc/routers/_app';const handler = (req: Request) =>fetchRequestHandler({endpoint: '/api/trpc',req,router: appRouter,createContext: createTRPCContext,});export { handler as GET, handler as POST };
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';import superjson from 'superjson';export function makeQueryClient() {return new QueryClient({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';import superjson from 'superjson';export function makeQueryClient() {return new QueryClient({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 konsumerar ditt tRPC-API från klientkomponenter. Importera här typdefinitionen av
din tRPC-router och skapa typsäkra hooks med createTRPCReact. Vi exporterar också vår kontextleverantör 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 { httpBatchLink } from '@trpc/client';import { createTRPCReact } from '@trpc/react-query';import { useState } from 'react';import { makeQueryClient } from './query-client';import type { AppRouter } from './routers/_app';export const trpc = createTRPCReact<AppRouter>();let clientQueryClientSingleton: QueryClient;function getQueryClient() {if (typeof window === 'undefined') {// Server: always make a new query clientreturn makeQueryClient();}// Browser: use singleton pattern to keep the same query clientreturn (clientQueryClientSingleton ??= makeQueryClient());}function getUrl() {const base = (() => {if (typeof window !== 'undefined') return '';if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;return 'http://localhost:3000';})();return `${base}/api/trpc`;}export function TRPCProvider(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 boundaryconst queryClient = 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';import { useState } from 'react';import { makeQueryClient } from './query-client';import type { AppRouter } from './routers/_app';export const trpc = createTRPCReact<AppRouter>();let clientQueryClientSingleton: QueryClient;function getQueryClient() {if (typeof window === 'undefined') {// Server: always make a new query clientreturn makeQueryClient();}// Browser: use singleton pattern to keep the same query clientreturn (clientQueryClientSingleton ??= makeQueryClient());}function getUrl() {const base = (() => {if (typeof window !== 'undefined') return '';if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;return 'http://localhost:3000';})();return `${base}/api/trpc`;}export function TRPCProvider(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 boundaryconst queryClient = 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>);}
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änta frågor från serverkomponenter använder vi en tRPC-caller. Modulen @trpc/react-query/rsc exporterar ett tunt lager runt
createCaller som integreras med din React Query-klient.
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 const getQueryClient = cache(makeQueryClient);const caller = createCallerFactory(appRouter)(createTRPCContext);export const { trpc, HydrateClient } = createHydrationHelpers<typeof appRouter>(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 const getQueryClient = cache(makeQueryClient);const caller = createCallerFactory(appRouter)(createTRPCContext);export const { trpc, HydrateClient } = createHydrationHelpers<typeof appRouter>(caller,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.
app/page.tsxtsximport { trpc } from '~/trpc/server';import { ClientGreeting } from './client-greeting';export default async function Home() {void trpc.hello.prefetch();return (<HydrateClient><div>...</div>{/** ... */}<ClientGreeting /></HydrateClient>);}
app/page.tsxtsximport { trpc } from '~/trpc/server';import { ClientGreeting } from './client-greeting';export default async function Home() {void trpc.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 function ClientGreeting() {const greeting = 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 function ClientGreeting() {const greeting = trpc.hello.useQuery();if (!greeting.data) return <div>Loading...</div>;return <div>{greeting.data.greeting}</div>;}
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 { trpc } from '~/trpc/server';import { Suspense } from 'react';import { ErrorBoundary } from 'react-error-boundary';import { ClientGreeting } from './client-greeting';export default async function Home() {void trpc.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 } from '~/trpc/server';import { Suspense } from 'react';import { ErrorBoundary } from 'react-error-boundary';import { ClientGreeting } from './client-greeting';export default async function Home() {void trpc.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 function ClientGreeting() {const [data] = trpc.hello.useSuspenseQuery();return <div>{data.greeting}</div>;}
app/client-greeting.tsxtsx'use client';import { trpc } from '~/trpc/client';export function ClientGreeting() {const [data] = trpc.hello.useSuspenseQuery();return <div>{data.greeting}</div>;}
Hämta data i en serverkomponent
Om du behöver åtkomst till data i en serverkomponent kan du anropa proceduren direkt istället för att använda .prefetch(), precis som med vanliga serveranrop. Observera att denna metod är avkopplad från din query-klient och inte lagrar data i cachen. Det innebär att du inte kan använda data i en serverkomponent och förvänta dig att den ska vara tillgänglig i klienten. Detta är avsiktligt och förklaras mer ingående i guiden Avancerad serverrendering.
app/page.tsxtsximport { trpc } from '~/trpc/server';export default async function Home() {// Use the caller directly without using `.prefetch()`const greeting = await trpc.hello();// ^? { greeting: string }return <div>{greeting.greeting}</div>;}
app/page.tsxtsximport { trpc } from '~/trpc/server';export default async function Home() {// Use the caller directly without using `.prefetch()`const greeting = await trpc.hello();// ^? { greeting: string }return <div>{greeting.greeting}</div>;}