React Server Componentsでのセットアップ
このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
これは「クラシック」なReact Query統合のドキュメントです(現在もサポートされていますが)、TanStack React Queryを使用した新しいtRPCプロジェクトを始める際の推奨方法ではありません。代わりに新しいTanStack React Query統合を使用することをお勧めします。
Next.jsを使用していますか? 推奨されるアプローチについては、専用のNext.js App Routerセットアップガイドをご覧ください。
このガイドは、Next.js App RouterのようなReact Server Components(RSC)フレームワークでtRPCを使用する方法の概要です。 RSC自体がtRPCが解決しようとしていた多くの問題を解決するため、tRPCが全く不要な場合もあることに留意してください。
tRPCとRSCを統合する方法は万能ではないため、このガイドを出発点として必要に応じて調整してください。
tRPCをServer Actionsと一緒に使用する方法をお探しの場合は、Server Actionsガイドをご覧ください。
進める前に、React Queryの高度なサーバーサイドレンダリングドキュメントを読んで、異なるタイプのサーバーレンダリングと避けるべき落とし穴を理解してください。
既存プロジェクトへのtRPC追加
1. 依存関係のインストール
- 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. tRPCルーターの作成
initTRPC関数を使用してtrpc/init.tsでtRPCバックエンドを初期化し、最初のルーターを作成します。ここではシンプルな「hello world」ルーターとプロシージャを作成しますが、tRPC API作成に関する詳細情報については、tRPCのクイックスタートガイドとバックエンド使用ドキュメントを参照してください。
ここで使用しているファイル名はtRPCによって強制されるものではありません。任意のファイル構造を使用できます。
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. Query Clientファクトリーの作成
共有ファイルtrpc/query-client.tsを作成し、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,},},});}
ここではいくつかのデフォルトオプションを設定しています:
-
staleTime: SSRを使用する場合、クライアントでの即時再取得を避けるため、通常はデフォルトのstaleTimeを0より大きい値に設定します。 -
shouldDehydrateQuery: クエリを脱水処理するかどうかを決定する関数です。RSCトランスポートプロトコルはネットワーク経由でのPromiseのハイドレートをサポートしているため、defaultShouldDehydrateQuery関数を拡張して、まだ保留中のクエリも含めるようにします。これにより、ツリー上位のサーバーコンポーネントでプリフェッチを開始し、そのPromiseを下位のクライアントコンポーネントで消費できるようになります。 -
serializeDataとdeserializeData(オプション): 前の手順でデータトランスフォーマーを設定した場合、サーバー-クライアント境界を越えてクエリクライアントをハイドレートする際にデータが正しくシリアライズされるよう、このオプションを設定します。
4. クライアントコンポーネント向けtRPCクライアントの作成
trpc/client.tsxはクライアントコンポーネントからtRPC APIを利用する際のエントリーポイントです。ここでtRPCルーターの型定義をインポートし、createTRPCReactを使用して型安全なフックを作成します。また、このファイルからコンテキストプロバイダーをエクスポートします。
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 >);}
アプリケーションのルート(例:Next.jsを使用する場合はapp/layout.tsx)にプロバイダーをマウントします。
5. サーバーコンポーネント向けtRPCコーラーの作成
サーバーコンポーネントからクエリをプリフェッチするには、tRPCコーラーを使用します。@trpc/react-query/rscモジュールは、React Queryクライアントと統合するcreateCallerのシンラッパーをエクスポートします。
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 ,);
APIの使用方法
これでアプリ内でtRPC APIを使用できます。クライアントコンポーネントでは他のReactアプリと同様にReact Queryフックを使用できますが、
RSCの機能を活用するにはツリー上位のサーバーコンポーネントでクエリをプリフェッチできます。これは「レンダリングしながらフェッチ」する
ローダーとして実装される一般的な概念で、リクエストを可能な限り早く開始しつつ、useQueryまたは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 >;}
Suspenseの活用
SuspenseとError Boundariesを使用してローディング状態とエラー状態を処理することをお勧めします。これは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 >;}
サーバーコンポーネントでのデータ取得
サーバーコンポーネントでデータにアクセスする必要がある場合、.prefetch()を使用する代わりに、通常のサーバーコーラーを使用するのと同様にプロシージャを直接呼び出せます。ただし、この方法はクエリクライアントから切り離されており、データをキャッシュに保存しないことに注意してください。つまり、サーバーコンポーネントでデータを使用してもクライアントで利用可能になることは期待できません。これは意図的な設計であり、高度なサーバーサイドレンダリングガイドで詳細に説明されています。
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 >;}