Skip to main content Accueil Créateurs trpc trpc nextjs-app-router
nextjs-app-router Full end-to-end tRPC setup for Next.js App Router. Covers route handler with fetchRequestHandler (GET + POST exports), TRPCProvider with QueryClientProvider, createTRPCOptionsProxy for RSC prefetching, HydrateClient/HydrationBoundary for hydration, useSuspenseQuery for Suspense, and server-side callers.
Aller à l'installation Skills Marketplace Découvrez et explorez les compétences IA créées par la communauté.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Copier le promptAfficher les détails du prompt Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
npx skills add https://github.com/trpc/trpc --skill nextjs-app-routerLa commande reste sur une seule ligne. Faites défiler horizontalement pour la vérifier avant de la copier.
Vous préférez une copie locale ? Téléchargez les fichiers actuellement disponibles dans SkillsMP.
Télécharger Zip Téléchargement... Configure the tRPC client link chain: httpLink, httpBatchLink, httpBatchStreamLink, splitLink, loggerLink, wsLink, createWSClient, httpSubscriptionLink, unstable_localLink, retryLink. Choose the right terminating link. Route subscriptions via splitLink. Build custom links for SOA routing. Link options: url, headers, transformer, maxURLLength, maxItems, connectionParams, EventSource ponyfill.
Métiers associés SOC
Basé sur la classification professionnelle SOC
name nextjs-app-router description Full end-to-end tRPC setup for Next.js App Router. Covers route handler with fetchRequestHandler (GET + POST exports), TRPCProvider with QueryClientProvider, createTRPCOptionsProxy for RSC prefetching, HydrateClient/HydrationBoundary for hydration, useSuspenseQuery for Suspense, and server-side callers.
type framework library trpc framework react library_version 11.16.0 requires ["server-setup","client-setup","react-query-setup","adapter-fetch"] sources ["www/docs/client/nextjs/overview.mdx","www/docs/client/tanstack-react-query/server-components.mdx","www/docs/server/adapters/nextjs.md","examples/next-prisma-starter/","examples/next-sse-chat/"]
This skill builds on [server-setup], [client-setup], [react-query-setup], and [adapter-fetch]. Read them first for foundational concepts.
tRPC -- Next.js App Router
File Structure
.
├── app
│ ├── api/trpc/[trpc]
│ │ └── route.ts # tRPC HTTP handler
│ ├── layout.tsx # mount TRPCReactProvider
│ ├── page.tsx # server component (prefetch)
│ └── client-greeting.tsx # client component (consume)
├── trpc
│ ├── init.ts # initTRPC, createTRPCContext
│ ├── routers
│ │ └── _app.ts # main app router, AppRouter type
│ ├── query-client.ts # shared QueryClient factory
│ ├── client.tsx # client hooks & TRPCReactProvider
│ └── server.tsx # server-side proxy & helpers
└── ...
Setup
1. Install
npm install @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query zod server-only client-only
2. Server init and context
import { initTRPC } from '@trpc/server' ;
export const createTRPCContext = async (opts : { headers: Headers } ) => {
return { userId : 'user_123' };
};
const t = initTRPC
.context <Awaited <ReturnType <typeof createTRPCContext>>>()
.create ();
export const createTRPCRouter = t.router ;
export const createCallerFactory = t.createCallerFactory ;
export const baseProcedure = t.procedure ;
3. Define the router
import { z } from 'zod' ;
{ baseProcedure, createTRPCRouter } ;
appRouter = ({
: baseProcedure
. (z. ({ : z. () }))
. ( ({
: ,
})),
});
= appRouter;
import
from
'../init'
export
const
createTRPCRouter
hello
input
object
text
string
query
({ input } ) =>
greeting
`hello ${input.text} `
export
type
AppRouter
typeof
4. Route handler (API endpoint) import { 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 ({ headers : req.headers }),
});
export { handler as GET , handler as POST };
5. QueryClient factory import {
defaultShouldDehydrateQuery,
QueryClient ,
} from '@tanstack/react-query' ;
export function makeQueryClient ( ) {
return new QueryClient ({
defaultOptions : {
queries : {
staleTime : 30 * 1000 ,
},
dehydrate : {
shouldDehydrateQuery : (query ) =>
defaultShouldDehydrateQuery (query) ||
query.state .status === 'pending' ,
},
},
});
}
If using a data transformer (e.g., superjson), add dehydrate.serializeData and hydrate.deserializeData here.
6. Client provider (client component) 'use client' ;
import 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, useTRPCClient } =
createTRPCContext<AppRouter >();
let browserQueryClient : QueryClient ;
function getQueryClient ( ) {
if (typeof window === 'undefined' ) {
return makeQueryClient ();
}
if (!browserQueryClient) browserQueryClient = makeQueryClient ();
return browserQueryClient;
}
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 TRPCReactProvider (props : { children: React.ReactNode } ) {
const queryClient = getQueryClient ();
const [trpcClient] = useState (() =>
createTRPCClient<AppRouter >({
links : [
httpBatchLink ({
url : getUrl (),
}),
],
}),
);
return (
<QueryClientProvider client ={queryClient} >
<TRPCProvider trpcClient ={trpcClient} queryClient ={queryClient} >
{props.children}
</TRPCProvider >
</QueryClientProvider >
);
}
7. Server-side proxy (server component) import 'server-only' ;
import { dehydrate, HydrationBoundary } from '@tanstack/react-query' ;
import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query' ;
import type { TRPCQueryOptions } 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' ;
export const getQueryClient = cache (makeQueryClient);
export const trpc = createTRPCOptionsProxy ({
ctx : async () =>
createTRPCContext ({
headers : await headers (),
}),
router : appRouter,
queryClient : getQueryClient,
});
export function HydrateClient (props : { children: React.ReactNode } ) {
const queryClient = getQueryClient ();
return (
<HydrationBoundary state ={dehydrate(queryClient)} >
{props.children}
</HydrationBoundary >
);
}
export function prefetch<T extends ReturnType <TRPCQueryOptions <any >>>(
queryOptions : T,
) {
const queryClient = getQueryClient ();
if (queryOptions.queryKey [1 ]?.type === 'infinite' ) {
void queryClient.prefetchInfiniteQuery (queryOptions as any );
} else {
void queryClient.prefetchQuery (queryOptions);
}
}
8. Mount provider in layout import { TRPCReactProvider } from '../trpc/client' ;
export default function RootLayout ({
children,
}: {
children: React.ReactNode;
} ) {
return (
<html lang ="en" >
<body >
<TRPCReactProvider > {children}</TRPCReactProvider >
</body >
</html >
);
}
Core Patterns
Prefetch in server component, consume in client component import { HydrateClient , prefetch, trpc } from '../trpc/server' ;
import { ClientGreeting } from './client-greeting' ;
export default async function Home ( ) {
prefetch (trpc.hello .queryOptions ({ text : 'world' }));
return (
<HydrateClient >
<ClientGreeting />
</HydrateClient >
);
}
'use client' ;
import { useQuery } from '@tanstack/react-query' ;
import { useTRPC } from '../trpc/client' ;
export function ClientGreeting ( ) {
const trpc = useTRPC ();
const greeting = useQuery (trpc.hello .queryOptions ({ text : 'world' }));
if (!greeting.data ) return <div > Loading...</div > ;
return <div > {greeting.data.greeting}</div > ;
}
Suspense with prefetch import { Suspense } from 'react' ;
import { ErrorBoundary } from 'react-error-boundary' ;
import { HydrateClient , prefetch, trpc } from '../trpc/server' ;
import { ClientGreeting } from './client-greeting' ;
export default async function Home ( ) {
prefetch (trpc.hello .queryOptions ({ text : 'world' }));
return (
<HydrateClient >
<ErrorBoundary fallback ={ <div > Something went wrong</div > }>
<Suspense fallback ={ <div > Loading...</div > }>
<ClientGreeting />
</Suspense >
</ErrorBoundary >
</HydrateClient >
);
}
'use client' ;
import { useSuspenseQuery } from '@tanstack/react-query' ;
import { useTRPC } from '../trpc/client' ;
export function ClientGreeting ( ) {
const trpc = useTRPC ();
const { data } = useSuspenseQuery (trpc.hello .queryOptions ({ text : 'world' }));
return <div > {data.greeting}</div > ;
}
Direct server caller (data needed on server only)
export const caller = appRouter.createCaller (async () =>
createTRPCContext ({ headers : await headers () }),
);
import { caller } from '../trpc/server' ;
export default async function Home ( ) {
const greeting = await caller.hello ({ text : 'world' });
return <div > {greeting.greeting}</div > ;
}
Note: caller results are not stored in the query cache. They cannot hydrate to client components. Use prefetchQuery if client components also need the data.
fetchQuery for data on server AND client import { getQueryClient, HydrateClient , trpc } from '../trpc/server' ;
import { ClientGreeting } from './client-greeting' ;
export default async function Home ( ) {
const queryClient = getQueryClient ();
const greeting = await queryClient.fetchQuery (
trpc.hello .queryOptions ({ text : 'world' }),
);
console .log (greeting.greeting );
return (
<HydrateClient >
<ClientGreeting />
</HydrateClient >
);
}
Common Mistakes
Not exporting both GET and POST from route handler Next.js App Router route handlers must export named GET and POST functions. Missing either causes queries or mutations to return 405 Method Not Allowed.
export default function handler (req : Request ) { ... }
const handler = (req : Request ) =>
fetchRequestHandler ({ req, router : appRouter, endpoint : '/api/trpc' , createContext });
export { handler as GET , handler as POST };
Creating a singleton QueryClient for SSR In server components, each request needs its own QueryClient instance. A singleton leaks data between requests.
const queryClient = new QueryClient ();
export const getQueryClient = cache (makeQueryClient);
The cache() wrapper from React ensures the same QueryClient is reused within a single request but a new one is created for each new request.
Missing dehydrate/shouldDehydrateQuery config RSC hydration requires shouldDehydrateQuery to include pending queries so that prefetched-but-not-yet-resolved promises can stream to the client. Without this, prefetched queries may not appear in the hydrated state.
new QueryClient ();
new QueryClient ({
defaultOptions : {
dehydrate : {
shouldDehydrateQuery : (query ) =>
defaultShouldDehydrateQuery (query) || query.state .status === 'pending' ,
},
},
});
Suspense query failure crashes entire page during SSR If a query fails during SSR with useSuspenseQuery, the entire page crashes. Error Boundaries only catch errors on the client side. For critical pages, either handle errors server-side before rendering, or use useQuery (non-suspense) which allows graceful degradation.
See Also
[react-query-setup] -- TanStack React Query setup, queryOptions/mutationOptions factories
[adapter-fetch] -- fetchRequestHandler for edge/serverless runtimes
[server-setup] -- initTRPC, routers, procedures, context
[nextjs-pages-router] -- if maintaining a Pages Router project alongside App Router