Skip to content
tRPC

Server-Side Rendering

On this page

To enable SSR just set ssr: true in your createTRPCNext config callback.

In order to execute queries properly during the server-side render step, we need to add extra logic inside our config. Additionally, consider Response Caching.

utils/trpc.ts
import { function httpBatchLink<TRouter extends AnyRouter>(opts: HTTPBatchLinkOptions<TRouter["_def"]["_config"]["$types"]>): TRPCLink<TRouter>httpBatchLink } from '@trpc/client';
import { function createTRPCNext<TRouter extends AnyRouter, TSSRContext extends NextPageContext = NextPageContext>(opts: WithTRPCNoSSROptions<TRouter> | WithTRPCSSROptions<TRouter>): CreateTRPCNext<TRouter, TSSRContext>createTRPCNext } from '@trpc/next';
import { const ssrPrepass: TRPCPrepassHelperssrPrepass } from '@trpc/next/ssrPrepass';
import type { 
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
} from './api/trpc/[trpc]';
export const
const trpc: CreateTRPCNextBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, NextPageContext> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
trpc
=
createTRPCNext<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, NextPageContext>(opts: WithTRPCNoSSROptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | WithTRPCSSROptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>>): CreateTRPCNextBase<...> & DecorateRouterRecord<...>
createTRPCNext
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
>({
ssr: true | ((opts: {
    ctx: NextPageContext;
}) => boolean | Promise<boolean>)
ssr
: true,
ssrPrepass: TRPCPrepassHelperssrPrepass,
config: (info: {
    ctx?: NextPageContext;
}) => WithTRPCConfig<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
config
(
info: {
    ctx?: NextPageContext;
}
info
) {
const { const ctx: NextPageContext | undefinedctx } =
info: {
    ctx?: NextPageContext;
}
info
;
if (typeof var window: Window & typeof globalThiswindow !== 'undefined') { // during client requests return {
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: '/api/trpc', }), ], }; } return {
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
({
// The server needs to know your app's full url url: string | URLurl: `${function getBaseUrl(): stringgetBaseUrl()}/api/trpc`, /** * Set custom request headers on every request from tRPC * @see https://trpc.io/docs/client/headers */
headers?: HTTPHeaders | ((opts: {
    opList: NonEmptyArray<Operation>;
}) => HTTPHeaders | Promise<HTTPHeaders>) | undefined
headers
() {
if (!const ctx: NextPageContext | undefinedctx?.NextPageContext.req?: IncomingMessage | undefinedreq?.IncomingMessage.headers: IncomingHttpHeaders | undefinedheaders) { return {}; } // To use SSR properly, you need to forward client headers to the server // This is so you can pass through things like cookies when we're server-side rendering return { cookie: string | undefinedcookie: const ctx: NextPageContextctx.NextPageContext.req?: IncomingMessagereq.IncomingMessage.headers: IncomingHttpHeadersheaders.IncomingHttpHeaders.cookie?: string | undefinedcookie, }; }, }), ], }; }, });

or, if you want to SSR conditional on a given request, you can pass a callback to ssr. This callback can return a boolean, or a Promise resolving to a boolean:

utils/trpc.ts
import { function httpBatchLink<TRouter extends AnyRouter>(opts: HTTPBatchLinkOptions<TRouter["_def"]["_config"]["$types"]>): TRPCLink<TRouter>httpBatchLink } from '@trpc/client';
import { function createTRPCNext<TRouter extends AnyRouter, TSSRContext extends NextPageContext = NextPageContext>(opts: WithTRPCNoSSROptions<TRouter> | WithTRPCSSROptions<TRouter>): CreateTRPCNext<TRouter, TSSRContext>createTRPCNext } from '@trpc/next';
import { const ssrPrepass: TRPCPrepassHelperssrPrepass } from '@trpc/next/ssrPrepass';
import type { 
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
} from './api/trpc/[trpc]';
export const
const trpc: CreateTRPCNextBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, NextPageContext> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
trpc
=
createTRPCNext<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, NextPageContext>(opts: WithTRPCNoSSROptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | WithTRPCSSROptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>>): CreateTRPCNextBase<...> & DecorateRouterRecord<...>
createTRPCNext
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
>({
ssrPrepass: TRPCPrepassHelperssrPrepass,
config: (info: {
    ctx?: NextPageContext;
}) => WithTRPCConfig<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
config
(
info: {
    ctx?: NextPageContext;
}
info
) {
const { const ctx: NextPageContext | undefinedctx } =
info: {
    ctx?: NextPageContext;
}
info
;
if (typeof var window: Window & typeof globalThiswindow !== 'undefined') { // during client requests return {
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: '/api/trpc', }), ], }; } return {
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
({
// The server needs to know your app's full url url: string | URLurl: `${function getBaseUrl(): stringgetBaseUrl()}/api/trpc`, /** * Set custom request headers on every request from tRPC * @see https://trpc.io/docs/client/headers */
headers?: HTTPHeaders | ((opts: {
    opList: NonEmptyArray<Operation>;
}) => HTTPHeaders | Promise<HTTPHeaders>) | undefined
headers
() {
if (!const ctx: NextPageContext | undefinedctx?.NextPageContext.req?: IncomingMessage | undefinedreq?.IncomingMessage.headers: IncomingHttpHeaders | undefinedheaders) { return {}; } // To use SSR properly, you need to forward client headers to the server // This is so you can pass through things like cookies when we're server-side rendering return { cookie: string | undefinedcookie: const ctx: NextPageContextctx.NextPageContext.req?: IncomingMessagereq.IncomingMessage.headers: IncomingHttpHeadersheaders.IncomingHttpHeaders.cookie?: string | undefinedcookie, }; }, }), ], }; },
ssr: true | ((opts: {
    ctx: NextPageContext;
}) => boolean | Promise<boolean>)
ssr
(
opts: {
    ctx: NextPageContext;
}
opts
) {
// only SSR if the request is coming from a bot return
opts: {
    ctx: NextPageContext;
}
opts
.ctx: NextPageContextctx?.NextPageContext.req?: IncomingMessage | undefinedreq?.IncomingMessage.headers: IncomingHttpHeadersheaders['user-agent']?.String.includes(searchString: string, position?: number): booleanincludes('bot') ?? false;
}, });
pages/_app.tsx
import { 
const trpc: CreateTRPCNextBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, NextPageContext> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
trpc
} from '../utils/trpc';
import type {
type AppProps<P = any> = AppInitialProps<P> & {
    Component: NextComponentType<NextPageContext, any, any>;
    router: Router;
    __N_SSG?: boolean;
    __N_SSP?: boolean;
}
AppProps
} from 'next/app';
import type {
type AppType<P = {}> = React.ComponentType<AppPropsType<any, P>> & {
    getInitialProps?(context: AppContextType<NextRouter>): P | Promise<P>;
}
AppType
} from 'next/app';
const const MyApp: AppTypeMyApp:
type AppType<P = {}> = React.ComponentType<AppPropsType<any, P>> & {
    getInitialProps?(context: AppContextType<NextRouter>): P | Promise<P>;
}
AppType
= ({ type Component: NextComponentType<NextPageContext, any, any>Component, pageProps: anypageProps }:
type AppProps<P = any> = AppInitialProps<P> & {
    Component: NextComponentType<NextPageContext, any, any>;
    router: Router;
    __N_SSG?: boolean;
    __N_SSP?: boolean;
}
AppProps
) => {
return <type Component: NextComponentType<NextPageContext, any, any>Component {...pageProps: anypageProps} />; }; export default
const trpc: CreateTRPCNextBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, NextPageContext> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
trpc
.CreateTRPCNextBase<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, NextPageContext>.withTRPC: (AppOrPage: NextComponentType<any, any, any>) => NextComponentTypewithTRPC(
const MyApp: React.FunctionComponent<AppPropsType<any, {}>> & {
    getInitialProps?(context: AppContextType<NextRouter>): {} | Promise<{}>;
}
MyApp
);

Response Caching with SSR

If you turn on SSR in your app, you might discover that your app loads slowly on, for instance, Vercel, but you can actually statically render your whole app without using SSG; read this Twitter thread for more insights.

You can use the responseMeta callback on createTRPCNext to set cache headers for SSR responses. See also the general Response Caching docs for framework-agnostic caching with responseMeta.

utils/trpc.tsx
import { function httpBatchLink<TRouter extends AnyRouter>(opts: HTTPBatchLinkOptions<TRouter["_def"]["_config"]["$types"]>): TRPCLink<TRouter>httpBatchLink } from '@trpc/client';
import { function createTRPCNext<TRouter extends AnyRouter, TSSRContext extends NextPageContext = NextPageContext>(opts: WithTRPCNoSSROptions<TRouter> | WithTRPCSSROptions<TRouter>): CreateTRPCNext<TRouter, TSSRContext>createTRPCNext } from '@trpc/next';
import { const ssrPrepass: TRPCPrepassHelperssrPrepass } from '@trpc/next/ssrPrepass';
import type { 
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
} from '../server/routers/_app';
export const
const trpc: CreateTRPCNextBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, NextPageContext> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
trpc
=
createTRPCNext<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, NextPageContext>(opts: WithTRPCNoSSROptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | WithTRPCSSROptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>>): CreateTRPCNextBase<...> & DecorateRouterRecord<...>
createTRPCNext
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
>({
config: (info: {
    ctx?: NextPageContext;
}) => WithTRPCConfig<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
config
() {
if (typeof var window: Window & typeof globalThiswindow !== 'undefined') { return {
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: '/api/trpc', }), ], }; } const const url: stringurl = var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvenv.string | undefinedVERCEL_URL ? `https://${var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvenv.stringVERCEL_URL}/api/trpc` : 'http://localhost:3000/api/trpc'; return {
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, }), ], }; },
ssr: true | ((opts: {
    ctx: NextPageContext;
}) => boolean | Promise<boolean>)
ssr
: true,
ssrPrepass: TRPCPrepassHelperssrPrepass,
responseMeta?: ((opts: {
    ctx: NextPageContext;
    clientErrors: TRPCClientError<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{}>>>[];
}) => ResponseMeta) | undefined
responseMeta
(
opts: {
    ctx: NextPageContext;
    clientErrors: TRPCClientError<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{}>>>[];
}
opts
) {
const {
const clientErrors: TRPCClientError<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>[]
clientErrors
} =
opts: {
    ctx: NextPageContext;
    clientErrors: TRPCClientError<BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{}>>>[];
}
opts
;
if (
const clientErrors: TRPCClientError<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>[]
clientErrors
.Array<T>.length: numberlength) {
// propagate http first error from API calls return { ResponseMeta.status?: number | undefinedstatus:
const clientErrors: TRPCClientError<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>[]
clientErrors
[0].TRPCClientError<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>>.data: Maybe<DefaultErrorData>data?.httpStatus: number | undefinedhttpStatus ?? 500,
}; } // 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}`, ], ]), }; }, });

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<[value: unknown]>(callback: (value: unknown) => void, delay?: number, value?: unknown): NodeJS.Timeout (+1 overload)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 do I need to forward the client's headers to the server manually? Why doesn't tRPC automatically do that for me?

While it's rare that you wouldn't want to forward the client's headers to the server when doing SSR, you might want to add things dynamically in the headers. Therefore, tRPC doesn't want to take responsibility for header keys colliding, etc.

Q: Why do I need to delete the connection header when using SSR on Node 18?

If you don't remove the connection header, the data fetching will fail with TRPCClientError: fetch failed because connection is a forbidden header name.

Q: Why do I still see network requests being made in the Network tab?

By default, @tanstack/react-query (which we use for the data fetching hooks) refetches data on mount and window refocus, even if it's already got initial data via SSR. This ensures data is always up-to-date. See the page on SSG if you'd like to disable this behavior.