Server-Side Helpers
On this page
The server-side helpers provide you with a set of helper functions that you can use to prefetch queries on the server. This is useful for SSG, but also for SSR if you opt not to use ssr: true.
Prefetching via the server-side helpers allows populating the query cache on the server, which means that these queries do not have to fetch on the client initially.
There are 2 ways to use the server-side helpers.
1. Internal router
This method is used when you have direct access to your tRPC router, e.g., when developing a monolithic Next.js application.
Using the helpers makes tRPC call your procedures directly on the server, without an HTTP request, similar to server-side calls.
That also means that you don't have the request and response at hand like you usually do. Make sure you're instantiating the server-side helpers with a context without req & res, which are typically filled via the context creation. We recommend the concept of "inner" and "outer" context in that scenario.
import { function createServerSideHelpers<TRouter extends AnyRouter>(opts: CreateServerSideHelpersOptions<TRouter>): ProtectedIntersection<{
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
}, DecoratedProcedureSSGRecord<TRouter["_def"]["_config"]["$types"], TRouter["_def"]["record"]>>
createServerSideHelpers } from '@trpc/react-query/server';
import { function createContext(): Promise<{}>createContext } from './server/context';
import { const appRouter: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{}>>
appRouter } from './server/routers/_app';
import class superjsonsuperjson from 'superjson';
const const helpers: {
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
} & DecoratedProcedureSSGRecord<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{}>>
helpers = createServerSideHelpers<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{}>>>(opts: CreateServerSideHelpersOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{}>>>): {
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
} & DecoratedProcedureSSGRecord<...>
createServerSideHelpers({
router: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{}>>
router: const appRouter: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{}>>
appRouter,
ctx: objectctx: await function createContext(): Promise<{}>createContext(),
transformer: DataTransformerOptionstransformer: class superjsonsuperjson,
});2. External router
This method is used when you don't have direct access to your tRPC router, e.g., when developing a Next.js application and a standalone API hosted separately.
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 createServerSideHelpers<TRouter extends AnyRouter>(opts: CreateServerSideHelpersOptions<TRouter>): ProtectedIntersection<{
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
}, DecoratedProcedureSSGRecord<TRouter["_def"]["_config"]["$types"], TRouter["_def"]["record"]>>
createServerSideHelpers } from '@trpc/react-query/server';
import type { type AppRouter = Router<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter } from './server/router';
import class superjsonsuperjson from 'superjson';
const const proxyClient: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
proxyClient = 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: 'http://localhost:3000/api/trpc',
}),
],
});
const const helpers: {
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
} & DecoratedProcedureSSGRecord<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>
helpers = createServerSideHelpers<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: CreateServerSideHelpersOptions<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>): {
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
} & DecoratedProcedureSSGRecord<...>
createServerSideHelpers({
client: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
client: const proxyClient: TRPCClient<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>>
proxyClient,
});Helpers usage
The server-side helpers methods return an object that mirrors your router structure, with all of your routers as keys. However, rather than useQuery and useMutation, you get prefetch, fetch, prefetchInfinite, and fetchInfinite functions.
The primary difference between prefetch and fetch is that fetch acts much like a normal function call, returning the result of the query, whereas prefetch does not return the result and never throws - if you need that behavior, use fetch instead. Instead, prefetch will add the query to the cache, which you then dehydrate and send to the client.
// In getServerSideProps / getStaticProps:
const const props: {
trpcState: DehydratedState;
}
props = {
// very important - use `trpcState` as the key
trpcState: DehydratedStatetrpcState: const helpers: {
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
} & DecoratedProcedureSSGRecord<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{}>>
helpers.dehydrate: (opts?: DehydrateOptions) => DehydratedStatedehydrate(),
};The rule of thumb is prefetch for queries that you know you'll need on the client, and fetch for queries that you want to use the result of on the server.
The functions are all wrappers around react-query functions. Please check out their docs to learn more about them in detail.
Next.js Example
import { function createServerSideHelpers<TRouter extends AnyRouter>(opts: CreateServerSideHelpersOptions<TRouter>): ProtectedIntersection<{
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
}, DecoratedProcedureSSGRecord<TRouter["_def"]["_config"]["$types"], TRouter["_def"]["record"]>>
createServerSideHelpers } from '@trpc/react-query/server';
import { const appRouter: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>>
appRouter } from './server/routers/_app';
import { const trpc: CreateTRPCReactBase<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>>, unknown> & DecorateRouterRecord<...>
trpc } from './utils/trpc';
import { 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 class superjsonsuperjson from 'superjson';
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 helpers: {
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
} & DecoratedProcedureSSGRecord<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>>
helpers = createServerSideHelpers<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>>>(opts: CreateServerSideHelpersOptions<...>): {
...;
} & DecoratedProcedureSSGRecord<...>
createServerSideHelpers({
router: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>>
router: const appRouter: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>>
appRouter,
ctx: objectctx: {},
transformer: DataTransformerOptionstransformer: class superjsonsuperjson,
});
const const id: stringid = context: GetServerSidePropsContext<{
id: string;
}>
context.params?: {
id: string;
} | undefined
params?.id: string | undefinedid as string;
/*
* Prefetching the `post.byId` query.
* `prefetch` does not return the result and never throws - if you need that behavior, use `fetch` instead.
*/
await const helpers: {
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
} & DecoratedProcedureSSGRecord<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>>
helpers.post: DecoratedProcedureSSGRecord<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
post.byId: Pick<DecorateQueryProcedure<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>>, SSGFns>
byId.function prefetch(input: {
id: string;
}, opts?: TRPCFetchQueryOptions<{
id: string;
title: string;
text: string;
createdAt: Date;
}, TRPCClientError<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}>> | undefined): Promise<void>
prefetch({ id: stringid });
// Make sure to return { props: { trpcState: helpers.dehydrate() } }
return {
props: {
trpcState: DehydratedState;
id: string;
}
props: {
trpcState: DehydratedStatetrpcState: const helpers: {
queryClient: QueryClient;
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
} & DecoratedProcedureSSGRecord<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>>
helpers.dehydrate: (opts?: DehydrateOptions) => DehydratedStatedehydrate(),
id: stringid,
},
};
}
export default function function PostViewPage(props: InferGetServerSidePropsType<typeof getServerSideProps>): JSX.ElementPostViewPage(
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 id: stringid } = props: {
trpcState: DehydratedState;
id: string;
}
props;
const const postQuery: UseTRPCQueryResult<{
id: string;
title: string;
text: string;
createdAt: Date;
}, TRPCClientErrorLike<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
transformer: true;
errorShape: DefaultErrorShape;
}>>
postQuery = const trpc: CreateTRPCReactBase<BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>>, unknown> & DecorateRouterRecord<...>
trpc.post: DecorateRouterRecord<{
ctx: object;
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
post.byId: DecoratedQuery<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
transformer: true;
errorShape: DefaultErrorShape;
}>
byId.useQuery: ProcedureUseQuery
<{
id: string;
title: string;
text: string;
createdAt: Date;
}, {
id: string;
title: string;
text: string;
createdAt: Date;
}>(input: {
id: string;
} | unique symbol, opts?: UseTRPCQueryOptions<{
id: string;
title: string;
text: string;
createdAt: Date;
}, {
id: string;
title: string;
text: string;
createdAt: Date;
}, TRPCClientErrorLike<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
transformer: true;
errorShape: DefaultErrorShape;
}>, {
id: string;
title: string;
text: string;
createdAt: Date;
}> | undefined) => UseTRPCQueryResult<...> (+1 overload)
useQuery({ id: stringid });
if (const postQuery: UseTRPCQueryResult<{
id: string;
title: string;
text: string;
createdAt: Date;
}, TRPCClientErrorLike<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
transformer: true;
errorShape: DefaultErrorShape;
}>>
postQuery.status: "error" | "success" | "pending"status !== 'success') {
// won't happen since the query has been prefetched
return <>Loading...</>;
}
const { const data: {
id: string;
title: string;
text: string;
createdAt: Date;
}
data } = const postQuery: (TRPCHookResult & QueryObserverSuccessResult<{
id: string;
title: string;
text: string;
createdAt: Date;
}, TRPCClientErrorLike<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
transformer: true;
errorShape: DefaultErrorShape;
}>>) | (TRPCHookResult & QueryObserverPlaceholderResult<{
id: string;
title: string;
text: string;
createdAt: Date;
}, TRPCClientErrorLike<{
input: {
id: string;
};
output: {
id: string;
title: string;
text: string;
createdAt: Date;
};
transformer: true;
errorShape: DefaultErrorShape;
}>>)
postQuery;
return (
<>
<JSX.IntrinsicElements.h1: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>{const data: {
id: string;
title: string;
text: string;
createdAt: Date;
}
data.title: stringtitle}</JSX.IntrinsicElements.h1: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>
<JSX.IntrinsicElements.em: DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>em>Created {const data: {
id: string;
title: string;
text: string;
createdAt: Date;
}
data.createdAt: DatecreatedAt.Date.toLocaleDateString(locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions): string (+2 overloads)toLocaleDateString()}</JSX.IntrinsicElements.em: DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>em>
<JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>{const data: {
id: string;
title: string;
text: string;
createdAt: Date;
}
data.text: stringtext}</JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
<JSX.IntrinsicElements.h2: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2>Raw data:</JSX.IntrinsicElements.h2: DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2>
<JSX.IntrinsicElements.pre: DetailedHTMLProps<HTMLAttributes<HTMLPreElement>, HTMLPreElement>pre>{var JSON: JSONJSON.JSON.stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string (+1 overload)stringify(const data: {
id: string;
title: string;
text: string;
createdAt: Date;
}
data, null, 4)}</JSX.IntrinsicElements.pre: DetailedHTMLProps<HTMLAttributes<HTMLPreElement>, HTMLPreElement>pre>
</>
);
}