Skip to content
tRPC

Server-Side Rendering

Prefetch a page's tRPC queries in getServerSideProps with createTRPCOptionsProxy, build the context from the request, and set the status and cache headers

On this page

To render a page's data on the server, prefetch its queries in getServerSideProps. Next.js then renders the page with the data in the cache, so it's in the first HTML, and the client doesn't fetch it again.

This page builds on the Pages Router setup.

Prefetch in getServerSideProps

In getServerSideProps, make a query client and a tRPC proxy that calls your router directly, with no HTTP request. Prefetch the queries the page needs, and return the cache as trpcState. _app's HydrationBoundary puts it into the client's cache.

pages/posts/[id].tsx
import { function dehydrate(client: QueryClient, options?: DehydrateOptions): DehydratedStatedehydrate, 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 createTRPCOptionsProxy<TRouter extends AnyRouter, TFeatureFlags extends FeatureFlags = {
    keyPrefix: false;
}>(opts: TRPCOptionsProxyOptions<TRouter, TFeatureFlags>): TRPCOptionsProxy<TRouter, TFeatureFlags>
createTRPCOptionsProxy
} from '@trpc/tanstack-react-query';
import type {
type GetServerSidePropsContext<Params extends ParsedUrlQuery = ParsedUrlQuery, Preview extends PreviewData = PreviewData> = {
    req: IncomingMessage & {
        cookies: NextApiRequestCookies;
    };
    res: ServerResponse;
    params?: Params;
    query: ParsedUrlQuery;
    preview?: boolean;
    previewData?: Preview;
    draftMode?: boolean;
    resolvedUrl: string;
    locale?: string;
    locales?: string[];
    defaultLocale?: string;
}
GetServerSidePropsContext
,
type InferGetServerSidePropsType<T extends (args: any) => any> = Extract<Awaited<ReturnType<T>>, {
    props: any;
}>["props"] extends null | undefined ? Extract<Awaited<ReturnType<T>>, {
    props: any;
}>["props"] : Extract<Awaited<ReturnType<T>>, {
    props: any;
}>["props"] extends object & {
    then(onfulfilled: infer F, ...args: infer _): any;
} ? F extends (value: infer V, ...args: infer _) => any ? Awaited<V> : never : Extract<Awaited<ReturnType<T>>, {
    props: any;
}>["props"]
InferGetServerSidePropsType
,
} from 'next'; import {
function createContext(opts: {
    headers: IncomingHttpHeaders;
}): Promise<{
    user: {
        id: string;
    } | null;
}>
createContext
} from '../../server/context';
import {
const appRouter: BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: {
            user: {
                id: string;
            } | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                text: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
} from '../../server/routers/_app';
import { function makeQueryClient(): QueryClientmakeQueryClient } from '../../utils/query-client'; import {
const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: {
            user: {
                id: string;
            } | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                text: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, {
    ...;
}>
useTRPC
} from '../../utils/trpc';
export async function
function getServerSideProps(context: GetServerSidePropsContext<{
    id: string;
}>): Promise<{
    props: {
        trpcState: DehydratedState;
        id: string;
    };
}>
getServerSideProps
(
context: GetServerSidePropsContext<{
    id: string;
}>
context
:
type GetServerSidePropsContext<Params extends ParsedUrlQuery = ParsedUrlQuery, Preview extends PreviewData = PreviewData> = {
    req: IncomingMessage & {
        cookies: NextApiRequestCookies;
    };
    res: ServerResponse;
    params?: Params;
    query: ParsedUrlQuery;
    preview?: boolean;
    previewData?: Preview;
    draftMode?: boolean;
    resolvedUrl: string;
    locale?: string;
    locales?: string[];
    defaultLocale?: string;
}
GetServerSidePropsContext
<{ id: stringid: string }>,
) { const const id: stringid =
context: GetServerSidePropsContext<{
    id: string;
}>
context
.
params?: {
    id: string;
} | undefined
params
?.id: string | undefinedid as string;
const const queryClient: QueryClientqueryClient = function makeQueryClient(): QueryClientmakeQueryClient(); const
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: {
            user: {
                id: string;
            } | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                text: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, {
    ...;
}>
trpc
=
createTRPCOptionsProxy<BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: {
            user: {
                id: string;
            } | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                text: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, {
    ...;
}>(opts: TRPCOptionsProxyOptions<...>): TRPCOptionsProxy<...>
createTRPCOptionsProxy
({
TRPCOptionsProxyOptionsInternal<BuiltRouter<{ ctx: { user: { id: string; } | null; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ post: BuiltRouter<{ ctx: { user: { id: string; } | null; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<...>>; }>>>.router: BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: {
            user: {
                id: string;
            } | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                text: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
router
:
const appRouter: BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: {
            user: {
                id: string;
            } | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                text: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
,
TRPCOptionsProxyOptionsInternal<BuiltRouter<{ ctx: { user: { id: string; } | null; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ post: BuiltRouter<{ ctx: { user: { id: string; } | null; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<...>>; }>>>.ctx: {
    user: {
        id: string;
    } | null;
} | (() => MaybePromise<{
    user: {
        id: string;
    } | null;
}>)
ctx
: await
function createContext(opts: {
    headers: IncomingHttpHeaders;
}): Promise<{
    user: {
        id: string;
    } | null;
}>
createContext
({ headers: IncomingHttpHeadersheaders:
context: GetServerSidePropsContext<{
    id: string;
}>
context
.
req: IncomingMessage & {
    cookies: NextApiRequestCookies;
}
req
.IncomingMessage.headers: IncomingHttpHeadersheaders }),
queryClient: QueryClient | (() => QueryClient)queryClient, }); await const queryClient: QueryClientqueryClient.
QueryClient.prefetchQuery<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    id: string;
    title: string;
    text: string;
}, TRPCQueryKeyWithoutPrefix>(options: FetchQueryOptions<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    id: string;
    title: string;
    text: string;
}, TRPCQueryKeyWithoutPrefix, never>): Promise<void>
prefetchQuery
(
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: {
            user: {
                id: string;
            } | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                text: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, {
    ...;
}>
trpc
.
post: DecoratedRouterRecord<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            text: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>, {
    ...;
}> & DecorateRouterKeyable<...> & DecorateHandleReaders<...>
post
.
byId: DecorateQueryProcedure<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        text: string;
    };
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> & Record<string, never>
byId
.
DecorateQueryProcedure<{ input: { id: string; }; output: { id: string; title: string; text: string; }; transformer: false; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>.queryOptions: TRPCQueryOptions
<{
    id: string;
    title: string;
    text: string;
}, {
    id: string;
    title: string;
    text: string;
}>(input: {
    id: string;
}, opts?: UnusedSkipTokenTRPCQueryOptionsIn<{
    id: string;
    title: string;
    text: string;
}, {
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    keyPrefix: false;
}> | undefined) => UnusedSkipTokenTRPCQueryOptionsOut<{
    id: string;
    title: string;
    text: string;
}, {
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    keyPrefix: false;
}> (+2 overloads)
queryOptions
({ id: stringid }));
return {
props: {
    trpcState: DehydratedState;
    id: string;
}
props
: {
trpcState: DehydratedStatetrpcState: function dehydrate(client: QueryClient, options?: DehydrateOptions): DehydratedStatedehydrate(const queryClient: QueryClientqueryClient), id: stringid, }, }; } export default function function PostPage(props: InferGetServerSidePropsType<typeof getServerSideProps>): JSX.ElementPostPage(
props: {
    trpcState: DehydratedState;
    id: string;
}
props
:
type InferGetServerSidePropsType<T extends (args: any) => any> = Extract<Awaited<ReturnType<T>>, {
    props: any;
}>["props"] extends null | undefined ? Extract<Awaited<ReturnType<T>>, {
    props: any;
}>["props"] : Extract<Awaited<ReturnType<T>>, {
    props: any;
}>["props"] extends object & {
    then(onfulfilled: infer F, ...args: infer _): any;
} ? F extends (value: infer V, ...args: infer _) => any ? Awaited<V> : never : Extract<Awaited<ReturnType<T>>, {
    props: any;
}>["props"]
InferGetServerSidePropsType
<typeof
function getServerSideProps(context: GetServerSidePropsContext<{
    id: string;
}>): Promise<{
    props: {
        trpcState: DehydratedState;
        id: string;
    };
}>
getServerSideProps
>,
) { const
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: {
            user: {
                id: string;
            } | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                text: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, {
    ...;
}>
trpc
=
function useTRPC(): TRPCOptionsProxy<BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: {
            user: {
                id: string;
            } | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                text: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, {
    ...;
}>
useTRPC
();
const
const postQuery: UseQueryResult<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
postQuery
=
useQuery<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    id: string;
    title: string;
    text: string;
}, TRPCQueryKeyWithoutPrefix>(options: UndefinedInitialDataOptions<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    id: string;
    title: string;
    text: string;
}, TRPCQueryKeyWithoutPrefix>, queryClient?: QueryClient): UseQueryResult<...> (+2 overloads)
useQuery
(
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: {
            user: {
                id: string;
            } | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                text: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, {
    ...;
}>
trpc
.
post: DecoratedRouterRecord<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            text: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>, {
    ...;
}> & DecorateRouterKeyable<...> & DecorateHandleReaders<...>
post
.
byId: DecorateQueryProcedure<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        text: string;
    };
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> & Record<string, never>
byId
.
DecorateQueryProcedure<{ input: { id: string; }; output: { id: string; title: string; text: string; }; transformer: false; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>.queryOptions: TRPCQueryOptions
<{
    id: string;
    title: string;
    text: string;
}, {
    id: string;
    title: string;
    text: string;
}>(input: {
    id: string;
}, opts?: UnusedSkipTokenTRPCQueryOptionsIn<{
    id: string;
    title: string;
    text: string;
}, {
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    keyPrefix: false;
}> | undefined) => UnusedSkipTokenTRPCQueryOptionsOut<{
    id: string;
    title: string;
    text: string;
}, {
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    keyPrefix: false;
}> (+2 overloads)
queryOptions
({ id: stringid:
props: {
    trpcState: DehydratedState;
    id: string;
}
props
.id: stringid }));
switch (
const postQuery: UseQueryResult<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
postQuery
.status: "pending" | "error" | "success"status) {
case 'pending': return <JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Loading...</JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>; case 'error': return <JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Error: {
const postQuery: QueryObserverRefetchErrorResult<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>> | QueryObserverLoadingErrorResult<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
postQuery
.
error: TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>
error
.TRPCClientErrorBase<DefaultErrorShape>.message: stringmessage}</JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>;
case 'success': return ( <JSX.IntrinsicElements.article: DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>article> <JSX.IntrinsicElements.h1: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>{
const postQuery: QueryObserverSuccessResult<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>> | QueryObserverPlaceholderResult<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
postQuery
.
data: {
    id: string;
    title: string;
    text: string;
}
data
.title: stringtitle}</JSX.IntrinsicElements.h1: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>
<JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>{
const postQuery: QueryObserverSuccessResult<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>> | QueryObserverPlaceholderResult<{
    id: string;
    title: string;
    text: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
postQuery
.
data: {
    id: string;
    title: string;
    text: string;
}
data
.text: stringtext}</JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
</JSX.IntrinsicElements.article: DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>article> ); } }

The page reads the query as it would without prefetching, so status is 'success' on the first render. Use the same input in both places: it's part of the query key.

prefetchQuery never throws, and the cache only sends queries that succeeded, so a failed query fetches again in the browser. To act on an error on the server, use fetchQuery, which returns the data or throws. See Set the status and cache headers.

Build the context from the request

The proxy calls your procedures in the same process, so there's no URL to set and no headers to forward. Build the context from context.req instead, as the API handler does. Sharing one createContext that only needs the headers keeps the two the same:

server/context.ts
import type { IncomingHttpHeaders } from 'node:http';
import { 
function getUserFromCookie(cookie: string | undefined): Promise<{
    id: string;
} | null>
getUserFromCookie
} from './auth';
export async function
function createContext(opts: {
    headers: IncomingHttpHeaders;
}): Promise<{
    user: {
        id: string;
    } | null;
}>
createContext
(
opts: {
    headers: IncomingHttpHeaders;
}
opts
: { headers: IncomingHttpHeadersheaders: IncomingHttpHeaders }) {
// verify the session here: headers come from the client, so don't // trust a user ID that's simply sent in one const
const user: {
    id: string;
} | null
user
= await
function getUserFromCookie(cookie: string | undefined): Promise<{
    id: string;
} | null>
getUserFromCookie
(
opts: {
    headers: IncomingHttpHeaders;
}
opts
.headers: IncomingHttpHeadersheaders.IncomingHttpHeaders.cookie?: string | undefinedcookie);
return {
user: {
    id: string;
} | null
user
};
}
pages/api/trpc/[trpc].ts
import * as import trpcNexttrpcNext from '@trpc/server/adapters/next';
import { 
function createContext(opts: {
    headers: IncomingHttpHeaders;
}): Promise<{
    user: {
        id: string;
    } | null;
}>
createContext
} from '../../../server/context';
import {
const appRouter: BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
} from '../../../server/routers/_app';
export default import trpcNexttrpcNext.
function createNextApiHandler<BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: NodeHTTPHandlerOptions<BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, trpcNext.NextApiRequest, trpcNext.NextApiResponse>): trpcNext.NextApiHandler
createNextApiHandler
({
BaseHandlerOptions<BuiltRouter<{ ctx: { user: { id: string; } | null; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, NextApiRequest>.router: BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
router
:
const appRouter: BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
,
createContext: NodeHTTPCreateContextFn<BuiltRouter<{
    ctx: {
        user: {
            id: string;
        } | null;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, trpcNext.NextApiRequest, trpcNext.NextApiResponse>
createContext
: (opts: NodeHTTPCreateContextFnOptions<trpcNext.NextApiRequest, trpcNext.NextApiResponse>opts) =>
function createContext(opts: {
    headers: IncomingHttpHeaders;
}): Promise<{
    user: {
        id: string;
    } | null;
}>
createContext
({ headers: IncomingHttpHeadersheaders: opts: NodeHTTPCreateContextFnOptions<trpcNext.NextApiRequest, trpcNext.NextApiResponse>opts.req: trpcNext.NextApiRequestreq.IncomingMessage.headers: IncomingHttpHeadersheaders }),
});
If your API runs on another server

Pass a tRPC client to createTRPCOptionsProxy instead of the router. The request now goes over HTTP, so it needs the API's full URL, and you forward the headers the API needs, such as the cookie:

pages/posts/[id].tsx
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 createTRPCOptionsProxy<TRouter extends AnyRouter, TFeatureFlags extends FeatureFlags = {
    keyPrefix: false;
}>(opts: TRPCOptionsProxyOptions<TRouter, TFeatureFlags>): TRPCOptionsProxy<TRouter, TFeatureFlags>
createTRPCOptionsProxy
} from '@trpc/tanstack-react-query';
import type {
type GetServerSidePropsContext<Params extends ParsedUrlQuery = ParsedUrlQuery, Preview extends PreviewData = PreviewData> = {
    req: IncomingMessage & {
        cookies: NextApiRequestCookies;
    };
    res: ServerResponse;
    params?: Params;
    query: ParsedUrlQuery;
    preview?: boolean;
    previewData?: Preview;
    draftMode?: boolean;
    resolvedUrl: string;
    locale?: string;
    locales?: string[];
    defaultLocale?: string;
}
GetServerSidePropsContext
} from 'next';
import type {
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
} from '../../server/routers/_app';
import { function makeQueryClient(): QueryClientmakeQueryClient } from '../../utils/query-client'; export async function function getServerSideProps(context: GetServerSidePropsContext): Promise<void>getServerSideProps(context: GetServerSidePropsContextcontext:
type GetServerSidePropsContext<Params extends ParsedUrlQuery = ParsedUrlQuery, Preview extends PreviewData = PreviewData> = {
    req: IncomingMessage & {
        cookies: NextApiRequestCookies;
    };
    res: ServerResponse;
    params?: Params;
    query: ParsedUrlQuery;
    preview?: boolean;
    previewData?: Preview;
    draftMode?: boolean;
    resolvedUrl: string;
    locale?: string;
    locales?: string[];
    defaultLocale?: string;
}
GetServerSidePropsContext
) {
const const queryClient: QueryClientqueryClient = function makeQueryClient(): QueryClientmakeQueryClient(); const
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, {
    keyPrefix: false;
}>
trpc
=
createTRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, {
    keyPrefix: false;
}>(opts: TRPCOptionsProxyOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, {
    keyPrefix: false;
}>): TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>, {
    keyPrefix: false;
}>
createTRPCOptionsProxy
({
TRPCOptionsProxyOptionsExternal<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>>.client: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | TRPCUntypedClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
client
:
createTRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: CreateTRPCClientOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>): TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>>
createTRPCClient
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
>({
links: TRPCLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>[]
links
: [
httpBatchLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: HTTPBatchLinkOptions<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>): TRPCLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
httpBatchLink
({
url: string | URLurl: 'https://api.example.com/trpc',
headers?: HTTPHeaders | ((opts: {
    opList: NonEmptyArray<Operation>;
}) => HTTPHeaders | Promise<HTTPHeaders>) | undefined
headers
: () => ({ cookie: string | undefinedcookie: context: GetServerSidePropsContextcontext.
req: IncomingMessage & {
    cookies: NextApiRequestCookies;
}
req
.IncomingMessage.headers: IncomingHttpHeadersheaders.IncomingHttpHeaders.cookie?: string | undefinedcookie }),
}), ], }), queryClient: QueryClient | (() => QueryClient)queryClient, }); // prefetch and dehydrate as above }

Set the status and cache headers

getServerSideProps gets the response as context.res, so you set headers on it directly. To send a 404 when a post doesn't exist, use fetchQuery, catch the TRPCError, and return notFound:

pages/posts/[id].tsx
import { function dehydrate(client: QueryClient, options?: DehydrateOptions): DehydratedStatedehydrate } from '@tanstack/react-query';
import { class TRPCErrorTRPCError } from '@trpc/server';
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 type {
type GetServerSidePropsContext<Params extends ParsedUrlQuery = ParsedUrlQuery, Preview extends PreviewData = PreviewData> = {
    req: IncomingMessage & {
        cookies: NextApiRequestCookies;
    };
    res: ServerResponse;
    params?: Params;
    query: ParsedUrlQuery;
    preview?: boolean;
    previewData?: Preview;
    draftMode?: boolean;
    resolvedUrl: string;
    locale?: string;
    locales?: string[];
    defaultLocale?: string;
}
GetServerSidePropsContext
} from 'next';
import {
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
} from '../../server/routers/_app';
import { function makeQueryClient(): QueryClientmakeQueryClient } from '../../utils/query-client'; export async function
function getServerSideProps(context: GetServerSidePropsContext<{
    id: string;
}>): Promise<{
    notFound: boolean;
    props?: undefined;
} | {
    props: {
        trpcState: DehydratedState;
        id: string;
    };
    notFound?: undefined;
}>
getServerSideProps
(
context: GetServerSidePropsContext<{
    id: string;
}>
context
:
type GetServerSidePropsContext<Params extends ParsedUrlQuery = ParsedUrlQuery, Preview extends PreviewData = PreviewData> = {
    req: IncomingMessage & {
        cookies: NextApiRequestCookies;
    };
    res: ServerResponse;
    params?: Params;
    query: ParsedUrlQuery;
    preview?: boolean;
    previewData?: Preview;
    draftMode?: boolean;
    resolvedUrl: string;
    locale?: string;
    locales?: string[];
    defaultLocale?: string;
}
GetServerSidePropsContext
<{ id: stringid: string }>,
) { const const id: stringid =
context: GetServerSidePropsContext<{
    id: string;
}>
context
.
params?: {
    id: string;
} | undefined
params
?.id: string | undefinedid as string;
const const queryClient: QueryClientqueryClient = function makeQueryClient(): QueryClientmakeQueryClient(); const
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, {
    ...;
}>
trpc
=
createTRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, {
    ...;
}>(opts: TRPCOptionsProxyOptions<...>): TRPCOptionsProxy<...>
createTRPCOptionsProxy
({
TRPCOptionsProxyOptionsInternal<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ post: BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<...>>; }>>>.router: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
router
:
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
,
TRPCOptionsProxyOptionsInternal<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ post: BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<...>>; }>>>.ctx: object | (() => MaybePromise<object>)ctx: {}, queryClient: QueryClient | (() => QueryClient)queryClient, }); try { await const queryClient: QueryClientqueryClient.
QueryClient.fetchQuery<{
    id: string;
    title: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    id: string;
    title: string;
}, TRPCQueryKeyWithoutPrefix, never>(options: FetchQueryOptions<{
    id: string;
    title: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    id: string;
    title: string;
}, TRPCQueryKeyWithoutPrefix, never>): Promise<{
    id: string;
    title: string;
}>
fetchQuery
(
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, {
    ...;
}>
trpc
.
post: DecoratedRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>, {
    ...;
}> & DecorateRouterKeyable<...> & DecorateHandleReaders<...>
post
.
byId: DecorateQueryProcedure<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
    };
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> & Record<string, never>
byId
.
DecorateQueryProcedure<{ input: { id: string; }; output: { id: string; title: string; }; transformer: false; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>.queryOptions: TRPCQueryOptions
<{
    id: string;
    title: string;
}, {
    id: string;
    title: string;
}>(input: {
    id: string;
}, opts?: UnusedSkipTokenTRPCQueryOptionsIn<{
    id: string;
    title: string;
}, {
    id: string;
    title: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    keyPrefix: false;
}> | undefined) => UnusedSkipTokenTRPCQueryOptionsOut<{
    id: string;
    title: string;
}, {
    id: string;
    title: string;
}, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    keyPrefix: false;
}> (+2 overloads)
queryOptions
({ id: stringid }));
} catch (function (local var) cause: unknowncause) { if (function (local var) cause: unknowncause instanceof class TRPCErrorTRPCError && function (local var) cause: TRPCErrorcause.TRPCError.code: "BAD_GATEWAY" | "BAD_REQUEST" | "CLIENT_CLOSED_REQUEST" | "CONFLICT" | "FORBIDDEN" | "GATEWAY_TIMEOUT" | "INTERNAL_SERVER_ERROR" | "METHOD_NOT_SUPPORTED" | "NOT_FOUND" | "NOT_IMPLEMENTED" | "PARSE_ERROR" | "PAYLOAD_TOO_LARGE" | "PAYMENT_REQUIRED" | "PRECONDITION_FAILED" | "PRECONDITION_REQUIRED" | "SERVICE_UNAVAILABLE" | "TIMEOUT" | "TOO_MANY_REQUESTS" | "UNAUTHORIZED" | "UNPROCESSABLE_CONTENT" | "UNSUPPORTED_MEDIA_TYPE"code === 'NOT_FOUND') { return { notFound: booleannotFound: true }; } throw function (local var) cause: unknowncause; } // cache the page for 1 day, and revalidate it once a second at most const const ONE_DAY_IN_SECONDS: numberONE_DAY_IN_SECONDS = 60 * 60 * 24;
context: GetServerSidePropsContext<{
    id: string;
}>
context
.res: ServerResponse<IncomingMessage>res.OutgoingMessage<IncomingMessage>.setHeader(name: string, value: number | string | readonly string[]): ServerResponse<IncomingMessage>setHeader(
'cache-control', `s-maxage=1, stale-while-revalidate=${const ONE_DAY_IN_SECONDS: numberONE_DAY_IN_SECONDS}`, ); return {
props: {
    trpcState: DehydratedState;
    id: string;
}
props
: {
trpcState: DehydratedStatetrpcState: function dehydrate(client: QueryClient, options?: DehydrateOptions): DehydratedStatedehydrate(const queryClient: QueryClientqueryClient), id: stringid, }, }; }

Only cache pages that look the same for everyone. If the page depends on the user's cookie, leave out the cache-control header. See also the general Response Caching docs.

API Response Caching with Next.js Adapter

You can also use responseMeta on the Next.js API handler to cache API responses directly:

pages/api/trpc/[trpc].ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
import * as import trpcNexttrpcNext from '@trpc/server/adapters/next';

export const 
const createContext: ({ req, res, }: trpcNext.CreateNextContextOptions) => Promise<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}>
createContext
= async ({
req: trpcNext.NextApiRequestreq, res: trpcNext.NextApiResponseres, }: import trpcNexttrpcNext.
type CreateNextContextOptions = {
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
    info: TRPCRequestInfo;
}
CreateNextContextOptions
) => {
return { req: trpcNext.NextApiRequestreq, res: trpcNext.NextApiResponseres, }; }; type
type Context = {
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}
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 createContext: ({ req, res, }: trpcNext.CreateNextContextOptions) => Promise<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}>
createContext
>>;
export const
const t: TRPCRootObject<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object, RuntimeConfigOptions<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object>, {
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.
TRPCBuilder<object, object>.context<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}>(): TRPCBuilder<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object>
context
<
type Context = {
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}
Context
>().
TRPCBuilder<{ req: NextApiRequest; res: NextApiResponse; }, object>.create<RuntimeConfigOptions<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object>>(opts?: RuntimeConfigOptions<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object> | undefined): TRPCRootObject<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object, RuntimeConfigOptions<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object>, {
    ...;
}>
create
();
export const
const appRouter: BuiltRouter<{
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: trpcNext.NextApiRequest;
            res: trpcNext.NextApiResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
=
const t: TRPCRootObject<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object, RuntimeConfigOptions<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object>, {
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ req: NextApiRequest; res: NextApiResponse; }, object, RuntimeConfigOptions<{ req: NextApiRequest; res: NextApiResponse; }, object>, { ...; }>.router: RouterBuilder
<{
    public: BuiltRouter<{
        ctx: {
            req: trpcNext.NextApiRequest;
            res: trpcNext.NextApiResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>(_: {
    public: BuiltRouter<{
        ctx: {
            req: trpcNext.NextApiRequest;
            res: trpcNext.NextApiResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}) => BuiltRouter<...>
router
({
public: BuiltRouter<{
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    slowQueryCached: QueryProcedure<{
        input: void;
        output: {
            lastUpdated: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
public
:
const t: TRPCRootObject<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object, RuntimeConfigOptions<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object>, {
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ req: NextApiRequest; res: NextApiResponse; }, object, RuntimeConfigOptions<{ req: NextApiRequest; res: NextApiResponse; }, object>, { ...; }>.router: RouterBuilder
<{
    slowQueryCached: QueryProcedure<{
        input: void;
        output: {
            lastUpdated: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    slowQueryCached: QueryProcedure<{
        input: void;
        output: {
            lastUpdated: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<{
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    slowQueryCached: QueryProcedure<{
        input: void;
        output: {
            lastUpdated: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
slowQueryCached: QueryProcedure<{
    input: void;
    output: {
        lastUpdated: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
slowQueryCached
:
const t: TRPCRootObject<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object, RuntimeConfigOptions<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object>, {
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ req: NextApiRequest; res: NextApiResponse; }, object, RuntimeConfigOptions<{ req: NextApiRequest; res: NextApiResponse; }, object>, { ...; }>.procedure: ProcedureBuilder<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
.
ProcedureBuilder<{ req: NextApiRequest; res: NextApiResponse; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
    lastUpdated: string;
}>(resolver: ProcedureResolver<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object, object, UnsetMarker, UnsetMarker, {
    lastUpdated: string;
}>): QueryProcedure<{
    input: void;
    output: {
        lastUpdated: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(async (
opts: ProcedureResolverOptions<{
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
}, object, object, UnsetMarker>
opts
) => {
await new
var Promise: PromiseConstructor
new <unknown>(executor: (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void) => Promise<unknown>
Promise
((resolve: (value: unknown) => voidresolve) => function setTimeout(callback: (_: void) => void, delay?: number): NodeJS.Timeout (+2 overloads)setTimeout(resolve: (value: unknown) => voidresolve, 5000));
return { lastUpdated: stringlastUpdated: new
var Date: DateConstructor
new () => Date (+3 overloads)
Date
().Date.toJSON(key?: any): stringtoJSON(),
}; }), }), }); export type
type AppRouter = Router<{
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: trpcNext.NextApiRequest;
            res: trpcNext.NextApiResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter
= typeof
const appRouter: BuiltRouter<{
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: trpcNext.NextApiRequest;
            res: trpcNext.NextApiResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
;
export default import trpcNexttrpcNext.
function createNextApiHandler<BuiltRouter<{
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: trpcNext.NextApiRequest;
            res: trpcNext.NextApiResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>>(opts: NodeHTTPHandlerOptions<...>): trpcNext.NextApiHandler
createNextApiHandler
({
BaseHandlerOptions<BuiltRouter<{ ...; }, DecorateCreateRouterOptions<...>>, NextApiRequest>.router: BuiltRouter<{
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: trpcNext.NextApiRequest;
            res: trpcNext.NextApiResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
router
:
const appRouter: BuiltRouter<{
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: trpcNext.NextApiRequest;
            res: trpcNext.NextApiResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
,
createContext: NodeHTTPCreateContextFn<BuiltRouter<{
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: trpcNext.NextApiRequest;
            res: trpcNext.NextApiResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>, trpcNext.NextApiRequest, trpcNext.NextApiResponse>
createContext
,
HTTPBaseHandlerOptions<BuiltRouter<{ ...; }, DecorateCreateRouterOptions<...>>, NextApiRequest>.responseMeta?: ResponseMetaFn<BuiltRouter<{
    ctx: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    public: BuiltRouter<{
        ctx: {
            req: trpcNext.NextApiRequest;
            res: trpcNext.NextApiResponse;
        };
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        slowQueryCached: QueryProcedure<{
            input: void;
            output: {
                lastUpdated: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>> | undefined
responseMeta
(
opts: {
    data: TRPCResponse<unknown, DefaultErrorShape>[];
    ctx?: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    } | undefined;
    paths: readonly string[] | undefined;
    info: TRPCRequestInfo | undefined;
    type: ProcedureType | "unknown";
    errors: TRPCError[];
    eagerGeneration: boolean;
}
opts
) {
const {
const ctx: {
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
} | undefined
ctx
, const paths: readonly string[] | undefinedpaths, const errors: TRPCError[]errors, const type: "query" | "mutation" | "subscription" | "unknown"type } =
opts: {
    data: TRPCResponse<unknown, DefaultErrorShape>[];
    ctx?: {
        req: trpcNext.NextApiRequest;
        res: trpcNext.NextApiResponse;
    } | undefined;
    paths: readonly string[] | undefined;
    info: TRPCRequestInfo | undefined;
    type: ProcedureType | "unknown";
    errors: TRPCError[];
    eagerGeneration: boolean;
}
opts
;
// assuming you have all your public routes with the keyword `public` in them const const allPublic: boolean | undefinedallPublic = const paths: readonly string[] | undefinedpaths && const paths: readonly string[]paths.ReadonlyArray<string>.every(predicate: (value: string, index: number, array: readonly string[]) => unknown, thisArg?: any): boolean (+1 overload)every((path: stringpath) => path: stringpath.String.includes(searchString: string, position?: number): booleanincludes('public')); // checking that no procedures errored const const allOk: booleanallOk = const errors: TRPCError[]errors.Array<T>.length: numberlength === 0; // checking we're doing a query request const const isQuery: booleanisQuery = const type: "query" | "mutation" | "subscription" | "unknown"type === 'query'; if (
const ctx: {
    req: trpcNext.NextApiRequest;
    res: trpcNext.NextApiResponse;
} | undefined
ctx
?.res: trpcNext.NextApiResponse | undefinedres && const allPublic: boolean | undefinedallPublic && const allOk: booleanallOk && const isQuery: booleanisQuery) {
// cache request for 1 day + revalidate once every second const const ONE_DAY_IN_SECONDS: numberONE_DAY_IN_SECONDS = 60 * 60 * 24; return { ResponseMeta.headers?: Headers | HTTPHeaders | undefinedheaders: new var Headers: new (init?: HeadersInit) => HeadersHeaders([ [ 'cache-control', `s-maxage=1, stale-while-revalidate=${const ONE_DAY_IN_SECONDS: numberONE_DAY_IN_SECONDS}`, ], ]), }; } return {}; }, });

FAQ

Q: Why does the client still fetch data the server prefetched?

TanStack React Query refetches stale data when a component mounts, when the window regains focus, and when the network reconnects. The query client from the setup keeps data fresh for 30 seconds with staleTime, so the first render uses what the server sent. Raise staleTime to fetch less, or see SSG to turn refetching off.

Q: Can I use useSuspenseQuery?

Yes, for queries the page prefetched. A query that isn't in the cache suspends during the server render too, and the server can't reach your API's relative URL. Prefetch it in getServerSideProps, or use useQuery, which only fetches in the browser.