Set up with TanStack Start
On this page
This guide shows how to use tRPC with TanStack Start: route loaders start queries, and components read them through handles.
tRPC adds no router integration of its own. Data crosses from the server to the browser through TanStack's SSR query integration, with your tRPC data transformer.
The SSE chat example is a complete TanStack Start app that uses handles in its loaders.
1. Install deps
npm install @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query @tanstack/react-router-ssr-query superjson zod2. Create a query client factory
Use your tRPC transformer to serialize query data, and set a staleTime above 0 so the browser doesn't refetch right after hydration:
import { class QueryClientQueryClient } from '@tanstack/react-query';
import class superjsonsuperjson from 'superjson';
export function function createQueryClient(): QueryClientcreateQueryClient() {
return new new QueryClient(config?: QueryClientConfig): QueryClientQueryClient({
QueryClientConfig.defaultOptions?: DefaultOptions<Error> | undefineddefaultOptions: {
DefaultOptions<Error>.queries?: OmitKeyof<QueryObserverOptions<unknown, Error, unknown, unknown, readonly unknown[], never>, "suspense" | "queryKey", "strictly"> | undefinedqueries: { staleTime?: StaleTimeFunction<unknown, Error, unknown, readonly unknown[]> | undefinedstaleTime: 30 * 1000 },
DefaultOptions<Error>.dehydrate?: DehydrateOptions | undefineddehydrate: { DehydrateOptions.serializeData?: TransformerFn | undefinedserializeData: class superjsonsuperjson.SuperJSON.serialize: (object: SuperJSONValue) => SuperJSONResultserialize },
DefaultOptions<Error>.hydrate?: {
deserializeData?: TransformerFn;
queries?: QueryOptions;
mutations?: MutationOptions<unknown, DefaultError, unknown, unknown>;
} | undefined
hydrate: { deserializeData?: TransformerFn | undefineddeserializeData: class superjsonsuperjson.SuperJSON.deserialize: <T = unknown>(payload: SuperJSONResult, options?: {
inPlace?: boolean;
}) => T
deserialize },
},
});
}3. Create an isomorphic tRPC client
During server rendering, call procedures in the same process with localLink. In the browser, call them over HTTP:
import { function createIsomorphicFn(): IsomorphicFnBasecreateIsomorphicFn } from '@tanstack/react-start';
import { function getRequest(): RequestgetRequest } from '@tanstack/react-start/server';
import {
function createTRPCClient<TRouter extends AnyRouter>(opts: CreateTRPCClientOptions<TRouter>): TRPCClient<TRouter>createTRPCClient,
function httpBatchStreamLink<TRouter extends AnyRouter>(opts: HTTPBatchStreamLinkOptions<TRouter["_def"]["_config"]["$types"]>): TRPCLink<TRouter>httpBatchStreamLink,
function localLink<TRouter extends AnyRouter>(opts: LocalLinkOptions<TRouter>): TRPCLink<TRouter>localLink,
type type TRPCLink<TInferrable extends InferrableClientTypes> = (opts: TRPCClientRuntime) => OperationLink<TInferrable>TRPCLink,
} from '@trpc/client';
import {
function createTRPCContext<TRouter extends AnyRouter, TFeatureFlags extends FeatureFlags = {
keyPrefix: false;
}>(): CreateTRPCContextResult<TRouter, TFeatureFlags>
createTRPCContext,
type type TRPCOptionsProxy<TRouter extends AnyTRPCRouter, TFeatureFlags extends FeatureFlags = { keyPrefix: false; }> = DecoratedRouterRecord<TRouter["_def"]["_config"]["$types"], TRouter["_def"]["record"], TFeatureFlags> & DecorateRouterKeyable<TFeatureFlags> & DecorateHandleReaders<TFeatureFlags>TRPCOptionsProxy,
} from '@trpc/tanstack-react-query';
import class superjsonsuperjson from 'superjson';
import { function createContext(opts: {
req: Request;
}): Promise<{
req: Request;
}>
createContext } from '~/server/context';
import { const appRouter: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>
appRouter, type type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter } from '~/server/routers/_app';
export const { const TRPCProvider: TRPCProviderType<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
TRPCProvider, const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
useTRPC } = createTRPCContext<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>(): CreateTRPCContextResult<...>
createTRPCContext<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>();
export type type TRPC = DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>, {
...;
}> & DecorateRouterKeyable<...> & DecorateHandleReaders<...>
TRPC = type TRPCOptionsProxy<TRouter extends AnyTRPCRouter, TFeatureFlags extends FeatureFlags = { keyPrefix: false; }> = DecoratedRouterRecord<TRouter["_def"]["_config"]["$types"], TRouter["_def"]["record"], TFeatureFlags> & DecorateRouterKeyable<TFeatureFlags> & DecorateHandleReaders<TFeatureFlags>TRPCOptionsProxy<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>;
const const getLinks: IsomorphicFn<[], TRPCLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>[], TRPCLink<...>[]>
getLinks = function createIsomorphicFn(): IsomorphicFnBasecreateIsomorphicFn()
.IsomorphicFnBase.server: <[], TRPCLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>[]>(serverImpl: () => TRPCLink<...>[]) => ServerOnlyFn<...>
server((): type TRPCLink<TInferrable extends InferrableClientTypes> = (opts: TRPCClientRuntime) => OperationLink<TInferrable>TRPCLink<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>[] => [
localLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>(opts: LocalLinkOptions<...>): TRPCLink<...>
localLink({
router: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>
router: const appRouter: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>
appRouter,
createContext: () => Promise<{
req: Request;
}>
createContext: () => function createContext(opts: {
req: Request;
}): Promise<{
req: Request;
}>
createContext({ req: Requestreq: function getRequest(): RequestgetRequest() }),
transformer: DataTransformerOptionstransformer: class superjsonsuperjson,
}),
])
.ServerOnlyFn<[], TRPCLink<BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<{ post: BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<...>>; }>>>[]>.client: <TRPCLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>[]>(clientImpl: () => TRPCLink<...>[]) => IsomorphicFn<...>
client((): type TRPCLink<TInferrable extends InferrableClientTypes> = (opts: TRPCClientRuntime) => OperationLink<TInferrable>TRPCLink<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>[] => [
httpBatchStreamLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>(opts: HTTPBatchStreamLinkOptions<...>): TRPCLink<...>
httpBatchStreamLink({ url: string | URLurl: '/api/trpc', transformer: DataTransformerOptionstransformer: class superjsonsuperjson }),
]);
export const const makeTRPCClient: () => TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>
makeTRPCClient = () =>
createTRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>(opts: CreateTRPCClientOptions<...>): TRPCClient<...>
createTRPCClient<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>({ links: TRPCLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>[]
links: const getLinks: () => TRPCLink<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>[]
getLinks() });The browser's link calls /api/trpc. Serve it from a server route with the fetch adapter:
import { function createFileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]["parentRoute"], TId extends RouteConstraints["TId"] = FileRoutesByPath[TFilePath]["id"], TPath extends RouteConstraints["TPath"] = FileRoutesByPath[TFilePath]["path"], TFullPath extends RouteConstraints["TFullPath"] = FileRoutesByPath[TFilePath]["fullPath"]>(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>["createRoute"]createFileRoute } from '@tanstack/react-router';
import { function fetchRequestHandler<TRouter extends AnyRouter>(opts: FetchHandlerRequestOptions<TRouter>): Promise<Response>fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { function createContext(opts: {
req: Request;
}): Promise<{
req: Request;
}>
createContext } from '~/server/context';
import { const appRouter: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>
appRouter } from '~/server/routers/_app';
const const handler: ({ request }: {
request: Request;
}) => Promise<Response>
handler = ({ request: Requestrequest }: { request: Requestrequest: Request }) =>
fetchRequestHandler<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>(opts: FetchHandlerRequestOptions<...>): Promise<Response>
fetchRequestHandler({
endpoint: stringendpoint: '/api/trpc',
req: Requestreq: request: Requestrequest,
BaseHandlerOptions<BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<{ post: BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<...>>; }>>, Request>.router: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>
router: const appRouter: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>
appRouter,
createContext: FetchCreateContextFn<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>
createContext,
});
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/api/trpc/$", "/api/trpc/$", "/api/trpc/$", "/api/trpc/$", undefined, ResolveParams<"/api/trpc/$">, AnyContext, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, {
GET: ({ request }: {
request: Request;
}) => Promise<Response>;
POST: ({ request }: {
request: Request;
}) => Promise<Response>;
}>
Route = createFileRoute<"/api/trpc/$", RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/api/trpc/$", "/api/trpc/$", "/api/trpc/$">(path?: "/api/trpc/$" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute('/api/trpc/$')({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPC; }, AnyContext, AnyContext, ... 6 more ..., undefined>, ... 12 more ..., { ...; }>.server?: RouteServerOptions<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/api/trpc/$", ResolveParams<"/api/trpc/$">, {}, undefined, AnyContext, AnyContext, AnyContext, unknown, {
GET: ({ request }: {
request: Request;
}) => Promise<Response>;
POST: ({ request }: {
request: Request;
}) => Promise<Response>;
}> | undefined
server: { RouteServerOptions<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPC; }, AnyContext, AnyContext, ... 6 more ..., undefined>, ... 8 more ..., { ...; }>.handlers?: Constrain<{
GET: ({ request }: {
request: Request;
}) => Promise<Response>;
POST: ({ request }: {
request: Request;
}) => Promise<Response>;
}, Partial<...> | ((opts: HandlersFnOpts<...>) => CustomHandlerFunctionsRecord<...>)> | undefined
handlers: { type GET: ({ request }: {
request: Request;
}) => Promise<Response>
GET: const handler: ({ request }: {
request: Request;
}) => Promise<Response>
handler, type POST: ({ request }: {
request: Request;
}) => Promise<Response>
POST: const handler: ({ request }: {
request: Request;
}) => Promise<Response>
handler } },
});4. Put the proxy in the router context
Create one proxy per router with createTRPCOptionsProxy, and pass it to loaders through the router context:
import { const createRouter: CreateRouterFncreateRouter } from '@tanstack/react-router';
import { function setupRouterSsrQueryIntegration<TRouter extends AnyRouter>(opts: Options<TRouter>): voidsetupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-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 { function createQueryClient(): QueryClientcreateQueryClient } from '~/lib/query-client';
import { const makeTRPCClient: () => TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>
makeTRPCClient, const TRPCProvider: TRPCProviderType<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
TRPCProvider } from '~/lib/trpc';
import type { type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter } from '~/server/routers/_app';
import { const routeTree: Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>
routeTree } from './routeTree.gen';
export function function getRouter(): RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
getRouter() {
const const queryClient: QueryClientqueryClient = function createQueryClient(): QueryClientcreateQueryClient();
const const trpcClient: TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>
trpcClient = function makeTRPCClient(): TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>
makeTRPCClient();
const const trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
trpc = createTRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>(opts: TRPCOptionsProxyOptions<...>): TRPCOptionsProxy<...>
createTRPCOptionsProxy<type AppRouter = Router<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter>({
TRPCOptionsProxyOptionsExternal<BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<{ post: BuiltRouter<{ ctx: { req: Request; }; meta: object; errorShape: DefaultErrorShape; transformer: true; }, DecorateCreateRouterOptions<...>>; }>>>.client: TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>> | TRPCUntypedClient<...>
client: const trpcClient: TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>
trpcClient,
queryClient: QueryClient | (() => QueryClient)queryClient,
});
const const router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
router = createRouter<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>(options: RouterConstructorOptions<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, ... 5 more ..., undefined>, "never", false, RouterHistory, Record<...>>): RouterCore<...>
createRouter({
routeTree?: Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined> | undefined
routeTree,
context: {
queryClient: QueryClient;
trpc: TRPC;
}
context: { queryClient: QueryClientqueryClient, trpc: TRPCtrpc },
defaultPreload?: false | "intent" | "viewport" | "render" | undefineddefaultPreload: 'intent',
// Let the query cache decide what's fresh: loaders always run, and fresh data is reused
defaultPreloadStaleTime?: number | undefineddefaultPreloadStaleTime: 0,
Wrap?: ((props: {
children: any;
}) => React.JSX.Element) | undefined
Wrap: (props: {
children: any;
}
props) => (
<const TRPCProvider: TRPCProviderType<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
TRPCProvider trpcClient: TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>
trpcClient={const trpcClient: TRPCClient<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>>
trpcClient} queryClient: QueryClientqueryClient={const queryClient: QueryClientqueryClient}>
{props: {
children: any;
}
props.children: anychildren}
</const TRPCProvider: TRPCProviderType<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
TRPCProvider>
),
});
setupRouterSsrQueryIntegration<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>>(opts: Options<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, ... 7 more ..., undefined>, "never", false, RouterHistory, Record<...>>>): void
setupRouterSsrQueryIntegration({ router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
router, queryClient: QueryClientqueryClient });
return const router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
router;
}Type the root route's context with createRootRouteWithContext<{ queryClient: QueryClient; trpc: TRPC }>().
5. Start queries in loaders
A loader starts queries and returns their handles as loader data. For each query, choose whether the loader waits for it. Starting a query compares the two. Every input should come from the route's params or search params: see Start every query from the URL.
- On the first visit, the loader runs on the server, and the data is sent with the page.
- On later navigations, the loader runs in the browser against the tab's cache. Fresh data is reused; stale data renders at once and refetches.
Without waiting: unstable_$prefetch
The loader returns at once, and the route renders before the data arrives:
import { function createFileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]["parentRoute"], TId extends RouteConstraints["TId"] = FileRoutesByPath[TFilePath]["id"], TPath extends RouteConstraints["TPath"] = FileRoutesByPath[TFilePath]["path"], TFullPath extends RouteConstraints["TFullPath"] = FileRoutesByPath[TFilePath]["fullPath"]>(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>["createRoute"]createFileRoute } from '@tanstack/react-router';
import { const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense } from 'react';
import { function Comments(props: {
comments: unstable_inferInfiniteQueryHandle<TRPC["post"]["comments"]>;
}): JSX.Element
Comments } from '~/components/comments';
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id/comments", "/posts/$id/comments", "/posts/$id/comments", "/posts/$id/comments", undefined, ResolveParams<"/posts/$id/comments">, AnyContext, AnyContext, AnyContext, {}, ({ context: { trpc }, params }: LoaderFnContext<Register, RootRoute<Register, ... 10 more ..., undefined>, ... 7 more ..., undefined>) => {
...;
}, ... 4 more ..., undefined>
Route = createFileRoute<"/posts/$id/comments", RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id/comments", "/posts/$id/comments", "/posts/$id/comments">(path?: "/posts/$id/comments" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute('/posts/$id/comments')({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPC; }, AnyContext, AnyContext, ... 6 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc }, params }: LoaderFnContext<Register, RootRoute<Register, ... 10 more ..., undefined>, ... 7 more ..., undefined>) => {
...;
}, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefined
loader: ({ LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPC; }, AnyContext, AnyContext, ... 6 more ..., undefined>, ... 7 more ..., undefined>.context: {
queryClient: QueryClient;
trpc: TRPC;
}
context: { trpc: TRPCtrpc }, params: {
id: string;
}
params }) => ({
comments: unstable_TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
comments: trpc: TRPCtrpc.post: DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>, {
...;
}> & DecorateRouterKeyable<...> & DecorateHandleReaders<...>
post.comments: DecorateQueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> & DecorateInfiniteQueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
comments.DecorateInfiniteQueryHandleVerbs<{ input: { postId: string; limit?: number | undefined; cursor?: number | null | undefined; }; output: { items: { id: string; text: string; }[]; nextCursor: number | null; }; transformer: true; errorShape: DefaultErrorShape; featureFlags: { ...; }; }>.unstable_$prefetchInfinite: (input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
}, opts?: unstable_TRPCPrefetchInfiniteOptions<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> | undefined) => unstable_TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
unstable_$prefetchInfinite(
{ postId: stringpostId: params: {
id: string;
}
params.id: stringid, limit?: number | undefinedlimit: 20 },
{ getNextPageParam: (last: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
}) => number | null
getNextPageParam: (last: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
}
last) => last: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
}
last.nextCursor: number | nullnextCursor },
),
}),
UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function CommentsPage(): JSX.ElementCommentsPage,
});
function function CommentsPage(): JSX.ElementCommentsPage() {
const const loaderData: {
comments: unstable_TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData = const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id/comments", "/posts/$id/comments", "/posts/$id/comments", "/posts/$id/comments", undefined, ResolveParams<"/posts/$id/comments">, AnyContext, AnyContext, AnyContext, {}, ({ context: { trpc }, params }: LoaderFnContext<Register, RootRoute<Register, ... 10 more ..., undefined>, ... 7 more ..., undefined>) => {
...;
}, ... 4 more ..., undefined>
Route.RouteExtensions<"/posts/$id/comments", "/posts/$id/comments">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {
readonly PostsIdCommentsRoute: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id/comments", "/posts/$id/comments", ... 13 more ..., undefined>;
}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<...> & OptionalStructuralSharing<...>) | undefined) => {
...;
}
useLoaderData();
return (
<const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense React.SuspenseProps.fallback?: React.ReactNodefallback={<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>Loading comments…</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>}>
<function Comments(props: {
comments: unstable_inferInfiniteQueryHandle<TRPC["post"]["comments"]>;
}): JSX.Element
Comments comments: unstable_TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
comments={const loaderData: {
comments: unstable_TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData.comments: unstable_TRPCInfiniteQueryHandle<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
comments} />
</const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense>
);
}- Wrap the reader in
Suspense.Commentssuspends until the data arrives. Without your own boundary, the suspension reaches the nearest boundary above it: the route'spendingComponent, or a parent route's. That can blank much more of the page than you want. - The loader never throws for these queries. If a request fails,
Commentsthrows, and the route'serrorComponent(or the router's default) shows the error. - Pass
getNextPageParamto infinite queries. The loader runs again on client navigations, and if the cached pages are stale, it refetches every page the user loaded. That needsgetNextPageParamfrom page two on.
Waiting: unstable_$ensure
The loader waits, so the route renders with the data in place:
import { function createFileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]["parentRoute"], TId extends RouteConstraints["TId"] = FileRoutesByPath[TFilePath]["id"], TPath extends RouteConstraints["TPath"] = FileRoutesByPath[TFilePath]["path"], TFullPath extends RouteConstraints["TFullPath"] = FileRoutesByPath[TFilePath]["fullPath"]>(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>["createRoute"]createFileRoute, function notFound(options?: NotFoundError): NotFoundErrornotFound } from '@tanstack/react-router';
import { function isTRPCClientError<TInferrable extends InferrableClientTypes>(cause: unknown): cause is TRPCClientError<TInferrable>isTRPCClientError } from '@trpc/client';
import { function Post(props: {
post: unstable_inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post } from '~/components/post';
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id", "/posts/$id", undefined, ResolveParams<"/posts/$id">, AnyContext, AnyContext, AnyContext, {}, ({ context: { trpc }, params }: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, ... 8 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>
Route = createFileRoute<"/posts/$id", RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id">(path?: "/posts/$id" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute('/posts/$id')({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPC; }, AnyContext, AnyContext, ... 6 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc }, params }: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, ... 8 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefined
loader: async ({ LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPC; }, AnyContext, AnyContext, ... 6 more ..., undefined>, ... 7 more ..., undefined>.context: {
queryClient: QueryClient;
trpc: TRPC;
}
context: { trpc: TRPCtrpc }, params: {
id: string;
}
params }) => {
const const post: unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post = await trpc: TRPCtrpc.post: DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>, {
...;
}> & DecorateRouterKeyable<...> & DecorateHandleReaders<...>
post.byId: DecorateQueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> & Record<string, never>
byId
.DecorateQueryHandleVerbs<{ input: { id: string; }; output: { id: string; title: string; }; transformer: true; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>.unstable_$ensure: (input: {
id: string;
}, opts?: unstable_TRPCPrefetchOptions | undefined) => Promise<unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>>
unstable_$ensure({ id: stringid: params: {
id: string;
}
params.id: stringid })
.Promise<unstable_TRPCQueryHandle<{ input: { id: string; }; output: { id: string; title: string; }; transformer: true; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>>.catch<never>(onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>>
catch((cause: anycause) => {
if (isTRPCClientError<InferrableClientTypes>(cause: unknown): cause is TRPCClientError<InferrableClientTypes>isTRPCClientError(cause: anycause) && cause: TRPCClientError<InferrableClientTypes>cause.TRPCClientError<InferrableClientTypes>.data: anydata?.code === 'NOT_FOUND') {
throw function notFound(options?: NotFoundError): NotFoundErrornotFound();
}
throw cause: anycause;
});
return { post: unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post };
},
UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function PostPage(): JSX.ElementPostPage,
});
function function PostPage(): JSX.ElementPostPage() {
const const loaderData: {
post: unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData = const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id", "/posts/$id", undefined, ResolveParams<"/posts/$id">, AnyContext, AnyContext, AnyContext, {}, ({ context: { trpc }, params }: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, ... 8 more ..., undefined>, ... 7 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>
Route.RouteExtensions<"/posts/$id", "/posts/$id">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {
readonly PostsIdRoute: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id", "/posts/$id", ... 11 more ..., undefined>;
}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<...> & OptionalStructuralSharing<...>) | undefined) => {
...;
}
useLoaderData();
return <function Post(props: {
post: unstable_inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post post: unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post={const loaderData: {
post: unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData.post: unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post} />;
}- No
Suspenseboundary is needed forPost: the data is in the cache before the route renders. - The navigation waits for the loader. On a slow request, the router shows the route's
pendingComponent, if it has one. - The loader can act on errors, as here with
notFound(). - The handle carries no data. To use the data in the loader, for example for the route's
head, read it withawait context.queryClient.ensureQueryData(context.trpc.unstable_$fromHandle(post))and return what you need. See Using the data in a loader.
To wait for some queries and stream others in the same loader, see Combining both: start every query first, then await.
6. Read handles in components
Components read handles with unstable_$fromHandle from useTRPC(). They're the same components you would write for the Next.js App Router, minus 'use client':
import { function useSuspenseQuery<TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = readonly unknown[]>(options: UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): UseSuspenseQueryResult<TData, TError>useSuspenseQuery } from '@tanstack/react-query';
import type { type unstable_inferQueryHandle<TProcedure extends DecorateQueryHandleVerbs<any>> = TProcedure["unstable_$prefetch"] extends (...args: any) => infer R ? R : anyunstable_inferQueryHandle } from '@trpc/tanstack-react-query';
import { const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
useTRPC, type type TRPC = DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>, {
...;
}> & DecorateRouterKeyable<...> & DecorateHandleReaders<...>
TRPC } from '~/lib/trpc';
export function function Post(props: {
post: unstable_inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post(props: {
post: unstable_inferQueryHandle<TRPC["post"]["byId"]>;
}
props: { post: unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post: type unstable_inferQueryHandle<TProcedure extends DecorateQueryHandleVerbs<any>> = TProcedure["unstable_$prefetch"] extends (...args: any) => infer R ? R : anyunstable_inferQueryHandle<type TRPC = DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>, {
...;
}> & DecorateRouterKeyable<...> & DecorateHandleReaders<...>
TRPC['post']['byId']> }) {
const const trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
trpc = function useTRPC(): TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
useTRPC();
const const postQuery: UseSuspenseQueryResult<{
id: string;
title: string;
}, ErrorOf<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>>
postQuery = useSuspenseQuery<{
id: string;
title: string;
}, ErrorOf<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, {
id: string;
title: string;
}, TRPCQueryKeyWithoutPrefix>(options: UseSuspenseQueryOptions<{
id: string;
title: string;
}, ErrorOf<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, {
id: string;
title: string;
}, TRPCQueryKeyWithoutPrefix>, queryClient?: QueryClient): UseSuspenseQueryResult<...>
useSuspenseQuery(const trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
trpc.DecorateHandleReaders<{ keyPrefix: false; }>.unstable_$fromHandle: <{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, {
id: string;
title: string;
}>(handle: unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, opts?: UnusedSkipTokenTRPCQueryOptionsIn<NoInfer<{
id: string;
title: string;
}>, {
id: string;
title: string;
}, ErrorOf<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, {
...;
}> | undefined) => UnusedSkipTokenTRPCQueryOptionsOut<...> (+1 overload)
unstable_$fromHandle(props: {
post: unstable_inferQueryHandle<TRPC["post"]["byId"]>;
}
props.post: unstable_TRPCQueryHandle<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
post));
return <React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>{const postQuery: UseSuspenseQueryResult<{
id: string;
title: string;
}, ErrorOf<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>>
postQuery.data: {
id: string;
title: string;
}
data.title: stringtitle}</React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>;
}route loader
├─ trpc.post.byId.unstable_$prefetch({ id }) → post handle → <Post post={post} />
└─ trpc.post.comments.unstable_$prefetchInfinite() → comments handle → <Comments comments={comments} />
Start every query from the URL
A loader only sees the URL, so give it everything a page's queries need: path params for what the page is about, and search params for filters, sort and page. With validateSearch and loaderDeps, the loader starts the right query on every navigation, before the route renders:
import { function useSuspenseQuery<TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = readonly unknown[]>(options: UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): UseSuspenseQueryResult<TData, TError>useSuspenseQuery } from '@tanstack/react-query';
import { function createFileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]["parentRoute"], TId extends RouteConstraints["TId"] = FileRoutesByPath[TFilePath]["id"], TPath extends RouteConstraints["TPath"] = FileRoutesByPath[TFilePath]["path"], TFullPath extends RouteConstraints["TFullPath"] = FileRoutesByPath[TFilePath]["fullPath"]>(path?: TFilePath): FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>["createRoute"]createFileRoute } from '@tanstack/react-router';
import { import zz } from 'zod';
import { const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
useTRPC } from '../lib/trpc';
export const const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts", "/posts", "/posts", "/posts", z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>, ResolveParams<"/posts">, AnyContext, AnyContext, AnyContext, ... 6 more ..., undefined>
Route = createFileRoute<"/posts", RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts", "/posts", "/posts">(path?: "/posts" | undefined): <TRegister, TSearchValidator, TParams, TRouteContextFn, TBeforeLoadFn, TLoaderDeps, TLoaderFn, TChildren, TSSR, TMiddlewares, THandlers>(options?: (ParamsOptions<...> & ... 1 more ... & UpdatableRouteOptions<...>) | undefined) => Route<...>
createFileRoute('/posts')({
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPC; }, AnyContext, ... 7 more ..., undefined>, ... 12 more ..., undefined>.validateSearch?: Constrain<z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>, AnyValidator, DefaultValidator>
validateSearch: import zz.function object<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}>(shape?: {
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>
object({
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>
sort: import zz.enum<readonly ["new", "top"]>(values: readonly ["new", "top"], params?: string | z.core.$ZodEnumParams): z.ZodEnum<{
new: "new";
top: "top";
}> (+1 overload)
export enum
enum(['new', 'top']).ZodType<any, any, $ZodEnumInternals<{ new: "new"; top: "top"; }>>.default(def: "new" | "top"): z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>> (+1 overload)
default('new'),
page: z.ZodDefault<z.ZodNumber>page: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber()._ZodNumber<$ZodNumberInternals<number>>.int(params?: string | z.core.$ZodCheckNumberFormatParams): z.ZodNumberint()._ZodNumber<$ZodNumberInternals<number>>.positive(params?: string | z.core.$ZodCheckGreaterThanParams): z.ZodNumberpositive().ZodType<any, any, $ZodNumberInternals<number>>.default(def: number): z.ZodDefault<z.ZodNumber> (+1 overload)default(1),
}),
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPC; }, AnyContext, AnyContext, ... 6 more ..., undefined>, ... 12 more ..., undefined>.loaderDeps?: ((opts: FullSearchSchemaOption<RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>>) => {
sort: "new" | "top";
page: number;
}) | undefined
loaderDeps: ({ search: {
sort: "new" | "top";
page: number;
}
search }) => search: {
sort: "new" | "top";
page: number;
}
search,
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPC; }, AnyContext, AnyContext, ... 6 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc }, deps }: LoaderFnContext<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts", ResolveParams<"/posts">, {
sort: "new" | "top";
page: number;
}, AnyContext, AnyContext, AnyContext, unknown, undefined>) => {
posts: unstable_TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
...;
};
}>;
}, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefined
loader: ({ LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPC; }, AnyContext, AnyContext, ... 6 more ..., undefined>, ... 7 more ..., undefined>.context: {
queryClient: QueryClient;
trpc: TRPC;
}
context: { trpc: TRPCtrpc }, deps: {
sort: "new" | "top";
page: number;
}
deps }) => ({
posts: unstable_TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
posts: trpc: TRPCtrpc.post: DecoratedRouterRecord<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>, {
...;
}> & DecorateRouterKeyable<...> & DecorateHandleReaders<...>
post.list: DecorateQueryProcedure<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}> & Record<string, never>
list.DecorateQueryHandleVerbs<{ input: { sort: "new" | "top"; page: number; }; output: { id: string; title: string; }[]; transformer: true; errorShape: DefaultErrorShape; featureFlags: { ...; }; }>.unstable_$prefetch: (input: {
sort: "new" | "top";
page: number;
}, opts?: unstable_TRPCPrefetchOptions | undefined) => unstable_TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
unstable_$prefetch(deps: {
sort: "new" | "top";
page: number;
}
deps),
}),
UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function PostsPage(): JSX.ElementPostsPage,
});
function function PostsPage(): JSX.ElementPostsPage() {
const const trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
trpc = function useTRPC(): TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
useTRPC();
const const loaderData: {
posts: unstable_TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData = const Route: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts", "/posts", "/posts", "/posts", z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<{
new: "new";
top: "top";
}>>;
page: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>, ResolveParams<"/posts">, AnyContext, AnyContext, AnyContext, ... 6 more ..., undefined>
Route.RouteExtensions<"/posts", "/posts">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, {
readonly PostsRoute: Route<Register, RootRoute<Register, undefined, {
queryClient: QueryClient;
trpc: TRPC;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts", "/posts", "/posts", "/posts", z.ZodObject<{
sort: z.ZodDefault<z.ZodEnum<...>>;
page: z.ZodDefault<...>;
}, z.core.$strip>, ... 10 more ..., undefined>;
}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<...> & OptionalStructuralSharing<...>) | undefined) => {
...;
}
useLoaderData();
const const postsQuery: UseSuspenseQueryResult<{
id: string;
title: string;
}[], ErrorOf<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>>
postsQuery = useSuspenseQuery<{
id: string;
title: string;
}[], ErrorOf<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, {
id: string;
title: string;
}[], TRPCQueryKeyWithoutPrefix>(options: UseSuspenseQueryOptions<{
id: string;
title: string;
}[], ErrorOf<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, {
id: string;
title: string;
}[], TRPCQueryKeyWithoutPrefix>, queryClient?: QueryClient): UseSuspenseQueryResult<...>
useSuspenseQuery(
const trpc: TRPCOptionsProxy<BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
post: BuiltRouter<{
ctx: {
req: Request;
};
meta: object;
errorShape: DefaultErrorShape;
transformer: true;
}, DecorateCreateRouterOptions<{
byId: QueryProcedure<{
input: {
id: string;
};
output: {
id: string;
title: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
comments: QueryProcedure<{
input: {
postId: string;
limit?: number | undefined;
cursor?: number | null | undefined;
};
output: {
items: {
id: string;
text: string;
}[];
nextCursor: number | null;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
list: QueryProcedure<...>;
}>>;
}>>, {
...;
}>
trpc.DecorateHandleReaders<{ keyPrefix: false; }>.unstable_$fromHandle: <{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}, {
id: string;
title: string;
}[]>(handle: unstable_TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, opts?: UnusedSkipTokenTRPCQueryOptionsIn<NoInfer<{
id: string;
title: string;
}[]>, {
id: string;
title: string;
}[], ErrorOf<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>, {
...;
}> | undefined) => UnusedSkipTokenTRPCQueryOptionsOut<...> (+1 overload)
unstable_$fromHandle(const loaderData: {
posts: unstable_TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>;
}
loaderData.posts: unstable_TRPCQueryHandle<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>
posts),
);
return (
<React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>ul>
{const postsQuery: UseSuspenseQueryResult<{
id: string;
title: string;
}[], ErrorOf<{
input: {
sort: "new" | "top";
page: number;
};
output: {
id: string;
title: string;
}[];
transformer: true;
errorShape: DefaultErrorShape;
featureFlags: {
keyPrefix: false;
};
}>>
postsQuery.data: {
id: string;
title: string;
}[]
data.Array<{ id: string; title: string; }>.map<JSX.Element>(callbackfn: (value: {
id: string;
title: string;
}, index: number, array: {
id: string;
title: string;
}[]) => JSX.Element, thisArg?: any): JSX.Element[]
map((post: {
id: string;
title: string;
}
post) => (
<React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>li React.Attributes.key?: React.Key | null | undefinedkey={post: {
id: string;
title: string;
}
post.id: stringid}>{post: {
id: string;
title: string;
}
post.title: stringtitle}</React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>li>
))}
</React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>ul>
);
}A link to /posts?sort=top starts the query for the top posts as the navigation begins. The back button brings back the last sort, and a shared link opens the same view. Keep the sort in useState and you lose all three, since the loader can't see component state.
/posts?sort=top&page=2 → trpc.post.list.unstable_$prefetch({ sort: 'top', page: 2 })
If a query can't take its input from the URL, for example because it needs an id from another query's result, reshape the procedure: see If the URL isn't enough, reshape the procedure.
Handles in beforeLoad
beforeLoad can return handles too, for example to start a query in a parent route and read it in child routes. TanStack Router checks that values returned from beforeLoad are serializable, and handles pass that check as they are: their query keys and inputs are typed from the procedure.
Don't return handles from server functions
A server function runs with its own QueryClient, which never reaches the page. Return data from server functions, and start queries in loaders. In development, tRPC warns when it renders a handle whose data never reached the page's QueryClient.