Skip to content
tRPC

Set up with Next.js App Router

On this page
.
├── src
│   ├── app
│   │   ├── api
│   │   │   └── trpc
│   │   │       └── [trpc]
│   │   │           └── route.ts  # <-- tRPC HTTP handler
│   │   ├── layout.tsx            # <-- mount TRPCReactProvider
│   │   └── page.tsx              # <-- server component
│   ├── trpc
│   │   ├── init.ts               # <-- tRPC server init & context
│   │   ├── routers
│   │   │   ├── _app.ts           # <-- main app router
│   │   │   ├── post.ts           # <-- sub routers
│   │   │   └── [..]
│   │   ├── client.tsx            # <-- client hooks & provider
│   │   ├── query-client.ts       # <-- shared QueryClient factory
│   │   └── server.tsx            # <-- server-side caller
│   └── [..]
└── [..]

Add tRPC to an existing Next.js App Router project

1. Install deps

pnpm add @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query@latest zod client-only server-only
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
bun add @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query@latest zod client-only server-only

2. Create a tRPC router

Initialize your tRPC backend in trpc/init.ts using the initTRPC function, and create your first router. We're going to make a simple "hello world" router and procedure here — for deeper information on creating your tRPC API, refer to the Quickstart guide and Backend usage docs.

trpc/init.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';

/**
 * This context creator accepts `headers` so it can be reused in both
 * the RSC server caller (where you pass `next/headers`) and the
 * API route handler (where you pass the request headers).
 */
export const 
const createTRPCContext: (opts: {
    headers: Headers;
}) => Promise<{
    userId: string;
}>
createTRPCContext
= async (
opts: {
    headers: Headers;
}
opts
: { headers: Headersheaders: Headers }) => {
// const user = await auth(opts.headers); return { userId: stringuserId: '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
const t: TRPCRootObject<{
    userId: string;
}, object, {}, {
    ctx: {
        userId: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC
.
TRPCBuilder<object, object>.context<{
    userId: string;
}>(): TRPCBuilder<{
    userId: string;
}, object>
context
<
type Awaited<T> = T extends null | undefined ? T : T extends object & {
    then(onfulfilled: infer F, ...args: infer _): any;
} ? F extends (value: infer V, ...args: infer _) => any ? Awaited<V> : never : T
Awaited
<type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : anyReturnType<typeof
const createTRPCContext: (opts: {
    headers: Headers;
}) => Promise<{
    userId: string;
}>
createTRPCContext
>>>()
.
TRPCBuilder<{ userId: string; }, object>.create<{}>(opts?: {} | undefined): TRPCRootObject<{
    userId: string;
}, object, {}, {
    ctx: {
        userId: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
({
/** * @see https://trpc.io/docs/server/data-transformers */ // transformer: superjson, }); // Base router and procedure helpers export const
const createTRPCRouter: RouterBuilder<{
    ctx: {
        userId: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
createTRPCRouter
=
const t: TRPCRootObject<{
    userId: string;
}, object, {}, {
    ctx: {
        userId: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ userId: string; }, object, {}, { ctx: { userId: string; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder<{
    ctx: {
        userId: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
;
export const
const createCallerFactory: RouterCallerFactory<{
    ctx: {
        userId: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
createCallerFactory
=
const t: TRPCRootObject<{
    userId: string;
}, object, {}, {
    ctx: {
        userId: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ userId: string; }, object, {}, { ctx: { userId: string; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.createCallerFactory: RouterCallerFactory<{
    ctx: {
        userId: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
createCallerFactory
;
export const
const baseProcedure: ProcedureBuilder<{
    userId: string;
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
baseProcedure
=
const t: TRPCRootObject<{
    userId: string;
}, object, {}, {
    ctx: {
        userId: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ userId: string; }, object, {}, { ctx: { userId: string; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{
    userId: string;
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
;

trpc/routers/_app.ts
import { import zz } from 'zod';
import { const baseProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>baseProcedure, 
const createTRPCRouter: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
createTRPCRouter
} from '../init';
export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
createTRPCRouter<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}): BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
createTRPCRouter
({
hello: QueryProcedure<{
    input: {
        text: string;
    };
    output: {
        greeting: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
hello
: const baseProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>baseProcedure
.
ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
    text: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    text: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<object, object, object, {
    text: string;
}, {
    text: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input
(
import zz.
function object<{
    text: z.ZodString;
}>(shape?: {
    text: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    text: z.ZodString;
}, z.core.$strip>
object
({
text: z.ZodStringtext: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), }), ) .
ProcedureBuilder<object, object, object, { text: string; }, { text: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
    greeting: string;
}>(resolver: ProcedureResolver<object, object, object, {
    text: string;
}, UnsetMarker, {
    greeting: string;
}>): QueryProcedure<{
    input: {
        text: string;
    };
    output: {
        greeting: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
((
opts: ProcedureResolverOptions<object, object, object, {
    text: string;
}>
opts
) => {
return { greeting: stringgreeting: `hello ${
opts: ProcedureResolverOptions<object, object, object, {
    text: string;
}>
opts
.
ProcedureResolverOptions<object, object, object, { text: string; }>.input: {
    text: string;
}
input
.text: stringtext}`,
}; }), }); // export type definition of API export type
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
;

3. Create the API route handler

With the App Router, use the fetch adapter to handle tRPC requests. Create a route handler that exports both GET and POST:

app/api/trpc/[trpc]/route.ts
import { function fetchRequestHandler<TRouter extends AnyRouter>(opts: FetchHandlerRequestOptions<TRouter>): Promise<Response>fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { 
const createTRPCContext: (opts: {
    headers: Headers;
}) => Promise<{
    userId: string;
}>
createTRPCContext
} from './trpc/init';
import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
} from './trpc/routers/_app';
const const handler: (req: Request) => Promise<Response>handler = (req: Requestreq: Request) =>
fetchRequestHandler<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: FetchHandlerRequestOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>): Promise<Response>
fetchRequestHandler
({
endpoint: stringendpoint: '/api/trpc', req: Requestreq,
BaseHandlerOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, Request>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
,
createContext?: FetchCreateContextFn<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | undefined
createContext
: () =>
function createTRPCContext(opts: {
    headers: Headers;
}): Promise<{
    userId: string;
}>
createTRPCContext
({ headers: Headersheaders: req: Requestreq.Request.headers: Headersheaders }),
}); export { const handler: (req: Request) => Promise<Response>handler as
const GET: (req: Request) => Promise<Response>
export GET
GET
, const handler: (req: Request) => Promise<Response>handler as
const POST: (req: Request) => Promise<Response>
export POST
POST
};

4. Create a Query Client factory

Create a shared file trpc/query-client.ts that exports a function that creates a QueryClient instance.

trpc/query-client.ts
import {
  function defaultShouldDehydrateQuery(query: Query): booleandefaultShouldDehydrateQuery,
  class QueryClientQueryClient,
} from '@tanstack/react-query';
import class superjsonsuperjson from 'superjson';

export function function makeQueryClient(): QueryClientmakeQueryClient() {
  return new new QueryClient(config?: QueryClientConfig): QueryClientQueryClient({
    QueryClientConfig.defaultOptions?: DefaultOptions<Error> | undefineddefaultOptions: {
      DefaultOptions<Error>.queries?: OmitKeyof<QueryObserverOptions<unknown, Error, unknown, unknown, readonly unknown[], never>, "suspense" | "queryKey", "strictly"> | undefinedqueries: {
        staleTime?: StaleTimeFunction<unknown, Error, unknown, readonly unknown[]> | undefinedstaleTime: 30 * 1000,
      },
      DefaultOptions<Error>.dehydrate?: DehydrateOptions | undefineddehydrate: {
        // serializeData: superjson.serialize,
        DehydrateOptions.shouldDehydrateQuery?: ((query: Query) => boolean) | undefinedshouldDehydrateQuery: (query: Query<unknown, Error, unknown, readonly unknown[]>query) =>
          function defaultShouldDehydrateQuery(query: Query): booleandefaultShouldDehydrateQuery(query: Query<unknown, Error, unknown, readonly unknown[]>query) ||
          query: Query<unknown, Error, unknown, readonly unknown[]>query.Query<unknown, Error, unknown, readonly unknown[]>.state: QueryState<unknown, Error>state.QueryState<unknown, Error>.status: QueryStatusstatus === 'pending',
      },
      
DefaultOptions<Error>.hydrate?: {
    deserializeData?: TransformerFn;
    queries?: QueryOptions;
    mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>;
} | undefined
hydrate
: {
// deserializeData: superjson.deserialize, }, }, }); }

We're setting a few default options here:

  • staleTime: With SSR, we usually want to set some default staleTime above 0 to avoid refetching immediately on the client.
  • shouldDehydrateQuery: This is a function that determines whether a query should be dehydrated or not. Since the RSC transport protocol supports hydrating promises over the network, we extend the defaultShouldDehydrateQuery function to also include queries that are still pending. This will allow us to start prefetching in a server component high up the tree, then consuming that promise in a client component further down.
  • serializeData and deserializeData (optional): If you set up a data transformer in the previous step, set this option to make sure the data is serialized correctly when hydrating the query client over the server-client boundary.

5. Create a tRPC client for Client Components

The trpc/client.tsx is the entrypoint when consuming your tRPC API from client components. In here, import the type definition of your tRPC router and create typesafe hooks using createTRPCContext. We'll also export our context provider from this file.

trpc/client.tsx
'use client';

// ^-- to make sure we can mount the Provider from a server component
import type { class QueryClientQueryClient } from '@tanstack/react-query';
import { const QueryClientProvider: ({ client, children }: QueryClientProviderProps) => React.JSX.ElementQueryClientProvider } from '@tanstack/react-query';
import { function createTRPCClient<TRouter extends AnyRouter>(opts: CreateTRPCClientOptions<TRouter>): TRPCClient<TRouter>createTRPCClient, function httpBatchLink<TRouter extends AnyRouter>(opts: HTTPBatchLinkOptions<TRouter["_def"]["_config"]["$types"]>): TRPCLink<TRouter>httpBatchLink } from '@trpc/client';
import { 
function createTRPCContext<TRouter extends AnyRouter, TFeatureFlags extends FeatureFlags = {
    keyPrefix: false;
}>(): CreateTRPCContextResult<TRouter, TFeatureFlags>
createTRPCContext
} from '@trpc/tanstack-react-query';
import { function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)useState } from 'react'; import { function makeQueryClient(): QueryClientmakeQueryClient } from './query-client'; import type {
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
} from './routers/_app';
export const {
const TRPCProvider: TRPCProviderType<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
TRPCProvider
,
const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
useTRPC
} =
createTRPCContext<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>(): CreateTRPCContextResult<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
createTRPCContext
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
>();
let let browserQueryClient: QueryClientbrowserQueryClient: class QueryClientQueryClient; function function getQueryClient(): QueryClientgetQueryClient() { if (typeof var window: Window & typeof globalThiswindow === 'undefined') { // Server: always make a new query client return function makeQueryClient(): QueryClientmakeQueryClient(); } // 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 client if (!let browserQueryClient: QueryClientbrowserQueryClient) let browserQueryClient: QueryClientbrowserQueryClient = function makeQueryClient(): QueryClientmakeQueryClient(); return let browserQueryClient: QueryClientbrowserQueryClient; } function function getUrl(): stringgetUrl() { const const base: stringbase = (() => { if (typeof var window: Window & typeof globalThiswindow !== 'undefined') return ''; if (var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvenv.string | undefinedVERCEL_URL) return `https://${var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvenv.stringVERCEL_URL}`; return 'http://localhost:3000'; })(); return `${const base: stringbase}/api/trpc`; } export function
function TRPCReactProvider(props: Readonly<{
    children: React.ReactNode;
}>): React.JSX.Element
TRPCReactProvider
(
props: Readonly<{
    children: React.ReactNode;
}>
props
: type Readonly<T> = { readonly [P in keyof T]: T[P]; }Readonly<{
children: React.ReactNodechildren: export namespace ReactReact.type React.ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal | Promise<AwaitedReactNode> | null | undefinedReactNode; }>, ) { // 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 boundary const const queryClient: QueryClientqueryClient = function getQueryClient(): QueryClientgetQueryClient(); const [
const trpcClient: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>
trpcClient
] =
useState<TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>>(initialState: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>> | (() => TRPCClient<...>)): [...] (+1 overload)
useState
(() =>
createTRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>(opts: CreateTRPCClientOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>): TRPCClient<...>
createTRPCClient
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
>({
links: TRPCLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>[]
links
: [
httpBatchLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>(opts: HTTPBatchLinkOptions<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>): TRPCLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>>
httpBatchLink
({
// transformer: superjson, <-- if you use a data transformer url: string | URLurl: function getUrl(): stringgetUrl(), }), ], }), ); return ( <const QueryClientProvider: ({ client, children }: QueryClientProviderProps) => React.JSX.ElementQueryClientProvider client: QueryClientclient={const queryClient: QueryClientqueryClient}> <
const TRPCProvider: TRPCProviderType<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
TRPCProvider
trpcClient: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>
trpcClient
={
const trpcClient: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>>
trpcClient
} queryClient: QueryClientqueryClient={const queryClient: QueryClientqueryClient}>
{
props: Readonly<{
    children: React.ReactNode;
}>
props
.children: React.ReactNodechildren}
</
const TRPCProvider: TRPCProviderType<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
TRPCProvider
>
</const QueryClientProvider: ({ client, children }: QueryClientProviderProps) => React.JSX.ElementQueryClientProvider> ); }

Mount the provider in the root layout of your application:

app/layout.tsx
import { 
function TRPCReactProvider(props: {
    children: React.ReactNode;
}): React.JSX.Element
TRPCReactProvider
} from '~/trpc/client';
export default function
function RootLayout({ children, }: Readonly<{
    children: React.ReactNode;
}>): React.JSX.Element
RootLayout
({
children: React.ReactNodechildren, }: type Readonly<T> = { readonly [P in keyof T]: T[P]; }Readonly<{ children: React.ReactNodechildren: export namespace ReactReact.type React.ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal | Promise<AwaitedReactNode> | null | undefinedReactNode; }>) { return ( <React.JSX.IntrinsicElements.html: React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement>html React.HTMLAttributes<HTMLHtmlElement>.lang?: string | undefinedlang="en"> <React.JSX.IntrinsicElements.body: React.DetailedHTMLProps<React.HTMLAttributes<HTMLBodyElement>, HTMLBodyElement>body> <
function TRPCReactProvider(props: {
    children: React.ReactNode;
}): React.JSX.Element
TRPCReactProvider
>{children: React.ReactNodechildren}</
function TRPCReactProvider(props: {
    children: React.ReactNode;
}): React.JSX.Element
TRPCReactProvider
>
</React.JSX.IntrinsicElements.body: React.DetailedHTMLProps<React.HTMLAttributes<HTMLBodyElement>, HTMLBodyElement>body> </React.JSX.IntrinsicElements.html: React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement>html> ); }

6. Create a tRPC caller for Server Components

To prefetch queries from server components, we create a proxy from our router. You can also pass in a client if your router is on a separate server.

trpc/server.tsx
import 'server-only'; // <-- ensure this file cannot be imported from the client

import { 
function createTRPCOptionsProxy<TRouter extends AnyRouter, TFeatureFlags extends FeatureFlags = {
    keyPrefix: false;
}>(opts: TRPCOptionsProxyOptions<TRouter, TFeatureFlags>): TRPCOptionsProxy<TRouter, TFeatureFlags>
createTRPCOptionsProxy
} from '@trpc/tanstack-react-query';
import { function headers(): Promise<ReadonlyHeaders>headers } from 'next/headers'; import { function cache<CachedFunction extends Function>(fn: CachedFunction): CachedFunctioncache } from 'react'; import {
const createTRPCContext: (opts: {
    headers: Headers;
}) => Promise<{
    userId: string;
}>
createTRPCContext
} from './init';
import { function makeQueryClient(): QueryClientmakeQueryClient } from './query-client'; import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
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 const getQueryClient: () => QueryClientgetQueryClient = cache<() => QueryClient>(fn: () => QueryClient): () => QueryClientcache(function makeQueryClient(): QueryClientmakeQueryClient); export const
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
trpc
=
createTRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>(opts: TRPCOptionsProxyOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>): TRPCOptionsProxy<...>
createTRPCOptionsProxy
({
TRPCOptionsProxyOptionsInternal<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ hello: QueryProcedure<{ input: { text: string; }; output: { greeting: string; }; meta: object; errorShape: DefaultErrorShape; }>; }>>>.ctx: object | (() => MaybePromise<object>)ctx: async () =>
function createTRPCContext(opts: {
    headers: Headers;
}): Promise<{
    userId: string;
}>
createTRPCContext
({
headers: Headersheaders: await function headers(): Promise<ReadonlyHeaders>headers(), }),
TRPCOptionsProxyOptionsInternal<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ hello: QueryProcedure<{ input: { text: string; }; output: { greeting: string; }; meta: object; errorShape: DefaultErrorShape; }>; }>>>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
,
queryClient: QueryClient | (() => QueryClient)queryClient: const getQueryClient: () => QueryClientgetQueryClient, }); // If your router is on a separate server, pass a client instead: // createTRPCOptionsProxy({ // client: createTRPCClient({ links: [httpLink({ url: '...' })] }), // queryClient: getQueryClient, // });

7. Make API requests

You're all set! You can now prefetch queries in server components and consume them in client components.

Prefetching in a Server Component

app/page.tsx
import { function dehydrate(client: QueryClient, options?: DehydrateOptions): DehydratedStatedehydrate, const HydrationBoundary: ({ children, options, state, queryClient }: HydrationBoundaryProps) => React.ReactElementHydrationBoundary } from '@tanstack/react-query';
import { function getQueryClient(): QueryClientgetQueryClient, 
const trpc: {
    hello: {
        queryOptions: (input: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
trpc
} from '~/trpc/server';
import { function ClientGreeting(): React.JSX.ElementClientGreeting } from './client-greeting'; export default async function function Home(): Promise<React.JSX.Element>Home() { const const queryClient: QueryClientqueryClient = function getQueryClient(): QueryClientgetQueryClient(); void const queryClient: QueryClientqueryClient.
QueryClient.prefetchQuery<{
    greeting: string;
}, Error, {
    greeting: string;
}, any[]>(options: FetchQueryOptions<{
    greeting: string;
}, Error, {
    greeting: string;
}, any[], never>): Promise<void>
prefetchQuery
(
const trpc: {
    hello: {
        queryOptions: (input: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
trpc
.
hello: {
    queryOptions: (input: {
        text: string;
    }) => {
        queryKey: any[];
        queryFn: () => Promise<{
            greeting: string;
        }>;
    };
}
hello
.
queryOptions: (input: {
    text: string;
}) => {
    queryKey: any[];
    queryFn: () => Promise<{
        greeting: string;
    }>;
}
queryOptions
({
text: stringtext: 'world', }), ); return ( <const HydrationBoundary: ({ children, options, state, queryClient }: HydrationBoundaryProps) => React.ReactElementHydrationBoundary HydrationBoundaryProps.state: DehydratedState | null | undefinedstate={function dehydrate(client: QueryClient, options?: DehydrateOptions): DehydratedStatedehydrate(const queryClient: QueryClientqueryClient)}> <function ClientGreeting(): React.JSX.ElementClientGreeting /> </const HydrationBoundary: ({ children, options, state, queryClient }: HydrationBoundaryProps) => React.ReactElementHydrationBoundary> ); }

Using data in a Client Component

app/client-greeting.tsx
'use client';

// <-- hooks can only be used in client components
import { function useQuery<TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = readonly unknown[]>(options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): DefinedUseQueryResult<TData, TError> (+2 overloads)useQuery } from '@tanstack/react-query';
import { 
function useTRPC(): {
    hello: {
        queryOptions: (input: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
useTRPC
} from '~/trpc/client';
export function function ClientGreeting(): React.JSX.ElementClientGreeting() { const
const trpc: {
    hello: {
        queryOptions: (input: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
trpc
=
function useTRPC(): {
    hello: {
        queryOptions: (input: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
useTRPC
();
const
const greeting: UseQueryResult<{
    greeting: string;
}, Error>
greeting
=
useQuery<{
    greeting: string;
}, Error, {
    greeting: string;
}, any[]>(options: UndefinedInitialDataOptions<{
    greeting: string;
}, Error, {
    greeting: string;
}, any[]>, queryClient?: QueryClient): UseQueryResult<{
    greeting: string;
}, Error> (+2 overloads)
useQuery
(
const trpc: {
    hello: {
        queryOptions: (input: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
trpc
.
hello: {
    queryOptions: (input: {
        text: string;
    }) => {
        queryKey: any[];
        queryFn: () => Promise<{
            greeting: string;
        }>;
    };
}
hello
.
queryOptions: (input: {
    text: string;
}) => {
    queryKey: any[];
    queryFn: () => Promise<{
        greeting: string;
    }>;
}
queryOptions
({ text: stringtext: 'world' }));
if (!
const greeting: UseQueryResult<{
    greeting: string;
}, Error>
greeting
.
data: {
    greeting: string;
} | undefined
data
) return <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>Loading...</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>;
return <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>{
const greeting: DefinedQueryObserverResult<{
    greeting: string;
}, Error> | QueryObserverPlaceholderResult<{
    greeting: string;
}, Error>
greeting
.
data: {
    greeting: string;
}
data
.greeting: stringgreeting}</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>;
}

Leveraging Suspense

You may prefer handling loading and error states using Suspense and Error Boundaries. You can do this by using the useSuspenseQuery hook.

app/page.tsx
import { 
function HydrateClient(props: {
    children: React.ReactNode;
}): React.JSX.Element
HydrateClient
, function prefetch(queryOptions: any): voidprefetch,
const trpc: {
    hello: {
        queryOptions: (input?: {
            text: string;
        }) => any;
    };
}
trpc
} from '~/trpc/server';
import { const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense } from 'react'; import { class ErrorBoundaryErrorBoundary } from 'react-error-boundary'; import { function ClientGreeting(): React.JSX.ElementClientGreeting } from './client-greeting'; export default async function function Home(): Promise<React.JSX.Element>Home() { function prefetch(queryOptions: any): voidprefetch(
const trpc: {
    hello: {
        queryOptions: (input?: {
            text: string;
        }) => any;
    };
}
trpc
.
hello: {
    queryOptions: (input?: {
        text: string;
    }) => any;
}
hello
.
queryOptions: (input?: {
    text: string;
}) => any
queryOptions
());
return ( <
function HydrateClient(props: {
    children: React.ReactNode;
}): React.JSX.Element
HydrateClient
>
<class ErrorBoundaryErrorBoundary fallback: React.ReactNodefallback={<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>Something went wrong</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>}> <const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense React.SuspenseProps.fallback?: React.ReactNodefallback={<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>Loading...</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>}> <function ClientGreeting(): React.JSX.ElementClientGreeting /> </const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense> </class ErrorBoundaryErrorBoundary> </
function HydrateClient(props: {
    children: React.ReactNode;
}): React.JSX.Element
HydrateClient
>
); }
app/client-greeting.tsx
'use client';

import { function useSuspenseQuery<TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = readonly unknown[]>(options: UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): UseSuspenseQueryResult<TData, TError>useSuspenseQuery } from '@tanstack/react-query';
import { 
function useTRPC(): {
    hello: {
        queryOptions: (input?: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
useTRPC
} from '~/trpc/client';
export function function ClientGreeting(): React.JSX.ElementClientGreeting() { const
const trpc: {
    hello: {
        queryOptions: (input?: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
trpc
=
function useTRPC(): {
    hello: {
        queryOptions: (input?: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
useTRPC
();
const {
const data: {
    greeting: string;
}
data
} =
useSuspenseQuery<{
    greeting: string;
}, Error, {
    greeting: string;
}, any[]>(options: UseSuspenseQueryOptions<{
    greeting: string;
}, Error, {
    greeting: string;
}, any[]>, queryClient?: QueryClient): UseSuspenseQueryResult<{
    greeting: string;
}, Error>
useSuspenseQuery
(
const trpc: {
    hello: {
        queryOptions: (input?: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
trpc
.
hello: {
    queryOptions: (input?: {
        text: string;
    }) => {
        queryKey: any[];
        queryFn: () => Promise<{
            greeting: string;
        }>;
    };
}
hello
.
queryOptions: (input?: {
    text: string;
}) => {
    queryKey: any[];
    queryFn: () => Promise<{
        greeting: string;
    }>;
}
queryOptions
());
return <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>{
const data: {
    greeting: string;
}
data
.greeting: stringgreeting}</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>;
}

Getting data in a Server Component

If you need access to the data in a server component, we recommend creating a server caller and using it directly. Please note that this method is detached from your query client and does not store the data in the cache. This means that you cannot use the data in a server component and expect it to be available in the client. This is intentional and explained in more detail in the Advanced Server Rendering guide.

trpc/server.tsx
import { function headers(): Promise<ReadonlyHeaders>headers } from 'next/headers';
import { 
const createTRPCContext: (opts: {
    headers: Headers;
}) => Promise<{
    userId: string;
}>
createTRPCContext
} from './init';
import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        } | undefined;
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
} from './routers/_app';
// ... export const
const caller: DecorateRouterRecord<DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        } | undefined;
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
caller
=
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        } | undefined;
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
.
Router<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ hello: QueryProcedure<{ input: { text: string; } | undefined; output: { greeting: string; }; meta: object; errorShape: DefaultErrorShape; }>; }>>.createCaller: (ctx: object | (() => MaybePromise<object>), options?: {
    onError?: RouterCallerErrorHandler<object> | undefined;
    signal?: AbortSignal;
} | undefined) => DecorateRouterRecord<DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            text: string;
        } | undefined;
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
createCaller
(async () =>
function createTRPCContext(opts: {
    headers: Headers;
}): Promise<{
    userId: string;
}>
createTRPCContext
({ headers: Headersheaders: await function headers(): Promise<ReadonlyHeaders>headers() }),
);
app/page.tsx
import { 
const caller: {
    hello: () => Promise<{
        greeting: string;
    }>;
}
caller
} from '~/trpc/server';
export default async function function Home(): Promise<React.JSX.Element>Home() { const greeting = await
const caller: {
    hello: () => Promise<{
        greeting: string;
    }>;
}
caller
.
hello: () => Promise<{
    greeting: string;
}>
hello
();
const greeting: {
    greeting: string;
}
return <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>{
const greeting: {
    greeting: string;
}
greeting
.greeting: stringgreeting}</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>;
}

If you really need to use the data both on the server as well as inside client components and understand the tradeoffs explained in the Advanced Server Rendering guide, you can use fetchQuery instead of prefetch to have the data both on the server as well as hydrating it down to the client:

app/page.tsx
import { function getQueryClient(): QueryClientgetQueryClient, 
function HydrateClient(props: {
    children: React.ReactNode;
}): React.JSX.Element
HydrateClient
,
const trpc: {
    hello: {
        queryOptions: (input?: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
trpc
} from '~/trpc/server';
import { function ClientGreeting(): React.JSX.ElementClientGreeting } from './client-greeting'; export default async function function Home(): Promise<React.JSX.Element>Home() { const const queryClient: QueryClientqueryClient = function getQueryClient(): QueryClientgetQueryClient(); const
const greeting: {
    greeting: string;
}
greeting
= await const queryClient: QueryClientqueryClient.
QueryClient.fetchQuery<{
    greeting: string;
}, Error, {
    greeting: string;
}, any[], never>(options: FetchQueryOptions<{
    greeting: string;
}, Error, {
    greeting: string;
}, any[], never>): Promise<{
    greeting: string;
}>
fetchQuery
(
const trpc: {
    hello: {
        queryOptions: (input?: {
            text: string;
        }) => {
            queryKey: any[];
            queryFn: () => Promise<{
                greeting: string;
            }>;
        };
    };
}
trpc
.
hello: {
    queryOptions: (input?: {
        text: string;
    }) => {
        queryKey: any[];
        queryFn: () => Promise<{
            greeting: string;
        }>;
    };
}
hello
.
queryOptions: (input?: {
    text: string;
}) => {
    queryKey: any[];
    queryFn: () => Promise<{
        greeting: string;
    }>;
}
queryOptions
());
// Do something with greeting on the server return ( <
function HydrateClient(props: {
    children: React.ReactNode;
}): React.JSX.Element
HydrateClient
>
<function ClientGreeting(): React.JSX.ElementClientGreeting /> </
function HydrateClient(props: {
    children: React.ReactNode;
}): React.JSX.Element
HydrateClient
>
); }

Next steps

Pages under App Router