Skip to content
tRPC

Partners

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 zod

2. 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:

src/lib/query-client.ts
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:

src/lib/trpc.ts
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>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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>, {
    ...;
}> & DecorateRouterKeyable<...>
TRPC
= type TRPCOptionsProxy<TRouter extends AnyTRPCRouter, TFeatureFlags extends FeatureFlags = { keyPrefix: false; }> = DecoratedRouterRecord<TRouter["_def"]["_config"]["$types"], TRouter["_def"]["record"], TFeatureFlags> & DecorateRouterKeyable<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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>[]
getLinks
() });

The browser's link calls /api/trpc. Serve it from a server route with the fetch adapter:

src/routes/api/trpc.$.ts
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
createContext
,
}); export const
const Route: Route<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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, {
    ...;
}>
Route
=
createFileRoute<"/api/trpc/$", RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., { ...; }>.server?: RouteServerOptions<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/api/trpc/$", ResolveParams<"/api/trpc/$">, {}, undefined, AnyContext, AnyContext, AnyContext, unknown, {
    ...;
}> | undefined
server
: {
RouteServerOptions<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 8 more ..., { ...; }>.handlers?: Constrain<{
    ...;
}, 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:

src/router.tsx
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter
} from '~/server/routers/_app';
import {
const routeTree: Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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: TRPCOptionsProxy<AppRouter>;
}, 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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpcClient
,
queryClient: QueryClient | (() => QueryClient)queryClient, }); const
const router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>
router
=
createRouter<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<string, any>>(options: RouterConstructorOptions<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>): RouterCore<...>
createRouter
({
routeTree?: Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {}, unknown, unknown, unknown, undefined> | undefined
routeTree
,
context: {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}
context
: { queryClient: QueryClientqueryClient,
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
},
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>, {
    ...;
}>
TRPCProvider
>
), });
setupRouterSsrQueryIntegration<RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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: TRPCOptionsProxy<AppRouter>;
}, ... 8 more ..., undefined>, "never", false, RouterHistory, Record<...>>>): void
setupRouterSsrQueryIntegration
({
router: RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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: TRPCOptionsProxy<AppRouter>;
}, 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: TRPCOptionsProxy<AppRouter> }>().

5. Load handles in loaders

A loader lists its queries as entries, loads them with loadHandles, and returns the handles as loader data. For each entry, choose whether the loader waits for it. Loading handles compares the options. 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.
Start queries in a loaderOpen all posts or another article, then come back. Within 5 seconds, the page uses the cache. After that, the data is stale. Each request takes its own time, a little different on every run.
Start queries with
Latency

The page shows at once, with skeletons until the data comes in. Stale data shows at once and refetches.

← All posts

Comments

Network

No requests yet.

Without waiting: prefetch entries

The load resolves at once, and the route renders before the data arrives:

src/routes/posts.$id.comments.tsx
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 loadHandles<TFeatureFlags extends FeatureFlags, TEntries extends Record<string, AnyTRPCEntry>>(trpc: DecorateRouterKeyable<TFeatureFlags>, entries: TEntries): Promise<TRPCLoadedHandles<TEntries>>loadHandles, function prefetchInfiniteEntry<TDef extends ResolverDef>(procedure: DecorateInfiniteQueryProcedure<TDef>, input: TDef["input"], opts?: TRPCInfiniteEntryOptions<TDef>): TRPCInfiniteQueryEntry<TDef, "prefetch">prefetchInfiniteEntry } from '@trpc/tanstack-react-query/unstable-handles';
import { const Suspense: React.ExoticComponent<React.SuspenseProps>Suspense } from 'react';
import { 
function Comments(props: {
    comments: inferInfiniteQueryHandle<TRPC["post"]["comments"]>;
}): JSX.Element
Comments
} from '~/components/comments';
export const
const Route: Route<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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, ... 6 more ..., undefined>
Route
=
createFileRoute<"/posts/$id/comments", RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc }, params }: LoaderFnContext<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id/comments", ResolveParams<"/posts/$id/comments">, {}, AnyContext, AnyContext, AnyContext, unknown, undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefined
loader
: async ({
LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 7 more ..., undefined>.context: {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}
context
: {
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
},
params: {
    id: string;
}
params
}) => {
const [
const handles: HandlesOf<{
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>
handles
] = await
loadHandles<FeatureFlags, {
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>(trpc: DecorateRouterKeyable<FeatureFlags>, entries: {
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}): Promise<...>
loadHandles
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
, {
comments: TRPCInfiniteQueryEntry<{
    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;
    };
}, "prefetch">
comments
:
prefetchInfiniteEntry<{
    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;
    };
}>(procedure: 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;
    };
}>, input: {
    ...;
}, opts?: TRPCInfiniteEntryOptions<...> | undefined): TRPCInfiniteQueryEntry<...>
prefetchInfiniteEntry
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
.
post: DecoratedRouterRecord<{
    ctx: {
        req: Request;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: true;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    byAuthor: QueryProcedure<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    comments: QueryProcedure<...>;
    list: QueryProcedure<...>;
}>, {
    ...;
}> & DecorateRouterKeyable<...>
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
,
{ 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 },
), }); return
const handles: HandlesOf<{
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>
handles
;
}, UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function CommentsPage(): JSX.ElementCommentsPage, }); function function CommentsPage(): JSX.ElementCommentsPage() { const
const loaderData: {
    comments: 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: TRPCOptionsProxy<AppRouter>;
}, 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, ... 6 more ..., undefined>
Route
.
RouteExtensions<"/posts/$id/comments", "/posts/$id/comments">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {
    readonly PostsIdCommentsRoute: Route<Register, RootRoute<Register, undefined, {
        queryClient: QueryClient;
        trpc: TRPCOptionsProxy<AppRouter>;
    }, AnyContext, ... 7 more ..., undefined>, ... 15 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: inferInfiniteQueryHandle<TRPC["post"]["comments"]>;
}): JSX.Element
Comments
comments: 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: 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: 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. Comments suspends until the data arrives. Without your own boundary, the suspension reaches the nearest boundary above it: the route's pendingComponent, or a parent route's. That can blank much more of the page than you want.
  • The load never rejects for these queries. If a request fails, Comments throws, and the route's errorComponent (or the router's default) shows the error.
  • Pass getNextPageParam to infinite queries. The loader runs again on client navigations, and if the cached pages are stale, it refetches every page the user loaded. That needs getNextPageParam from page two on.

Waiting: ensure entries

The loader waits, so the route renders with the data in place:

src/routes/posts.$id.tsx
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 ensureEntry<TDef extends ResolverDef>(procedure: DecorateQueryProcedure<TDef>, ...args: EntryArgs<TDef>): TRPCQueryEntry<TDef, "ensure">ensureEntry,
  function isTRPCEntryError<TDef extends ResolverDef>(error: unknown, procedure: DecorateQueryProcedure<TDef>): error is TRPCEntryError<TDef> (+1 overload)isTRPCEntryError,
  function loadHandles<TFeatureFlags extends FeatureFlags, TEntries extends Record<string, AnyTRPCEntry>>(trpc: DecorateRouterKeyable<TFeatureFlags>, entries: TEntries): Promise<TRPCLoadedHandles<TEntries>>loadHandles,
} from '@trpc/tanstack-react-query/unstable-handles';
import { 
function Post(props: {
    post: inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post
} from '~/components/post';
export const
const Route: Route<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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, ... 8 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>
Route
=
createFileRoute<"/posts/$id", RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc }, params }: LoaderFnContext<Register, ... 8 more ..., undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefinedloader: async ({
LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 7 more ..., undefined>.context: {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}
context
: {
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
},
params: {
    id: string;
}
params
}) => {
const [
const handles: HandlesOf<{
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "ensure">;
}>
handles
] = await
loadHandles<FeatureFlags, {
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "ensure">;
}>(trpc: DecorateRouterKeyable<FeatureFlags>, entries: {
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "ensure">;
}): Promise<...>
loadHandles
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
, {
post: TRPCQueryEntry<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}, "ensure">
post
:
ensureEntry<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>(procedure: DecorateQueryProcedure<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, input: {
    id: string;
}, opts?: TRPCEntryOptions | undefined): TRPCQueryEntry<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}, "ensure">
ensureEntry
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
.
post: DecoratedRouterRecord<{
    ctx: {
        req: Request;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: true;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    byAuthor: QueryProcedure<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    comments: QueryProcedure<...>;
    list: QueryProcedure<...>;
}>, {
    ...;
}> & DecorateRouterKeyable<...>
post
.
byId: DecorateQueryProcedure<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> & Record<string, never>
byId
, { id: stringid:
params: {
    id: string;
}
params
.id: stringid }),
}).
Promise<TRPCLoadedHandles<{ post: TRPCQueryEntry<{ input: { id: string; }; output: { id: string; title: string; authorId: string; }; transformer: true; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }, "ensure">; }>>.catch<never>(onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<TRPCLoadedHandles<{
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "ensure">;
}>>
catch
((cause: anycause) => {
if (
isTRPCEntryError<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>(error: unknown, procedure: DecorateQueryProcedure<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>): error is TRPCEntryError<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> (+1 overload)
isTRPCEntryError
(cause: anycause,
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
.
post: DecoratedRouterRecord<{
    ctx: {
        req: Request;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: true;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    byAuthor: QueryProcedure<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    comments: QueryProcedure<...>;
    list: QueryProcedure<...>;
}>, {
    ...;
}> & DecorateRouterKeyable<...>
post
.
byId: DecorateQueryProcedure<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> & Record<string, never>
byId
) &&
cause: TRPCEntryError<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
cause
.TRPCEntryError<{ input: { id: string; }; output: { id: string; title: string; authorId: string; }; transformer: true; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>.code: "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "UNAUTHORIZED" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST" | undefinedcode === 'NOT_FOUND') {
throw function notFound(options?: NotFoundError): NotFoundErrornotFound(); } throw cause: anycause; }); return
const handles: HandlesOf<{
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "ensure">;
}>
handles
;
}, UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function PostPage(): JSX.ElementPostPage, }); function function PostPage(): JSX.ElementPostPage() { const
const loaderData: {
    post: TRPCQueryHandle<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
}
loaderData
=
const Route: Route<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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, ... 8 more ..., undefined>) => Promise<...>, ... 4 more ..., undefined>
Route
.
RouteExtensions<"/posts/$id", "/posts/$id">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {
    readonly PostsIdRoute: Route<Register, RootRoute<Register, undefined, {
        queryClient: QueryClient;
        trpc: TRPCOptionsProxy<AppRouter>;
    }, AnyContext, ... 7 more ..., undefined>, ... 15 more ..., undefined>;
}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<...> & OptionalStructuralSharing<...>) | undefined) => {
    ...;
}
useLoaderData
();
return <
function Post(props: {
    post: inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post
post: TRPCQueryHandle<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
post
={
const loaderData: {
    post: TRPCQueryHandle<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
}
loaderData
.
post: TRPCQueryHandle<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
post
} />;
}
  • The data is in the cache before the route renders, so Post shows the post at once. If the cache loses the post later, Post suspends and shows the nearest Suspense fallback, or the route's pendingComponent: see When the cache loses the data.
  • 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 from the load's data: const [handles, data] = await loadHandles(trpc, …), and return what you need. See Using the data in a loader.

To wait for some queries and stream others in the same loader, list them all in one load: see Waiting for some, streaming others.

6. Read handles in components

Components read handles with fromHandle and useTRPC(). They're the same components you would write for the Next.js App Router, minus 'use client':

src/components/post.tsx
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 fromHandle<TDef extends ResolverDef, TData = TDef["output"]>(trpc: DecorateRouterKeyable<NoInfer<TDef["featureFlags"]>>, handle: TRPCQueryHandle<TDef>, opts?: UnusedSkipTokenTRPCQueryOptionsIn<NoInfer<TDef["output"]>, TData, ErrorOf<TDef>, TDef["featureFlags"]>): UnusedSkipTokenTRPCQueryOptionsOut<TDef["output"], TData, ErrorOf<TDef>, TDef["featureFlags"]> (+1 overload)fromHandle } from '@trpc/tanstack-react-query/unstable-handles';
import type { type inferQueryHandle<TProcedure extends DecorateQueryProcedure<any>> = TProcedure extends DecorateQueryProcedure<infer TDef extends ResolverDef> ? TRPCQueryHandle<TDef> : neverinferQueryHandle } from '@trpc/tanstack-react-query/unstable-handles';
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>, {
    ...;
}> & DecorateRouterKeyable<...>
TRPC
} from '~/lib/trpc';
export function
function Post(props: {
    post: inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post
(
props: {
    post: inferQueryHandle<TRPC["post"]["byId"]>;
}
props
: {
post: TRPCQueryHandle<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
post
: type inferQueryHandle<TProcedure extends DecorateQueryProcedure<any>> = TProcedure extends DecorateQueryProcedure<infer TDef extends ResolverDef> ? TRPCQueryHandle<TDef> : neverinferQueryHandle<
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>, {
    ...;
}> & DecorateRouterKeyable<...>
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>, {
    ...;
}>
useTRPC
();
const
const postQuery: UseSuspenseQueryResult<{
    id: string;
    title: string;
    authorId: string;
}, ErrorOf<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>>
postQuery
=
useSuspenseQuery<{
    id: string;
    title: string;
    authorId: string;
}, ErrorOf<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, {
    id: string;
    title: string;
    authorId: string;
}, TRPCQueryKeyWithoutPrefix>(options: UseSuspenseQueryOptions<{
    id: string;
    title: string;
    authorId: string;
}, ErrorOf<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, {
    id: string;
    title: string;
    authorId: string;
}, TRPCQueryKeyWithoutPrefix>, queryClient?: QueryClient): UseSuspenseQueryResult<...>
useSuspenseQuery
(
fromHandle<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}, {
    id: string;
    title: string;
    authorId: string;
}>(trpc: DecorateRouterKeyable<NoInfer<{
    keyPrefix: false;
}>>, handle: TRPCQueryHandle<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, opts?: UnusedSkipTokenTRPCQueryOptionsIn<NoInfer<{
    id: string;
    title: string;
    authorId: string;
}>, {
    id: string;
    title: string;
    authorId: string;
}, ErrorOf<...>, {
    keyPrefix: false;
}> | undefined): UnusedSkipTokenTRPCQueryOptionsOut<...> (+1 overload)
fromHandle
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>, {
    ...;
}>
trpc
,
props: {
    post: inferQueryHandle<TRPC["post"]["byId"]>;
}
props
.
post: TRPCQueryHandle<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: 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;
    authorId: string;
}, ErrorOf<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>>
postQuery
.
data: {
    id: string;
    title: string;
    authorId: string;
}
data
.title: stringtitle}</React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>;
}

Handles

post: { queryKey, input }comments: { queryKey, input }

None yet

Components

<Post post={post} />

Not rendered yet

<Comments comments={comments} />

Not rendered yet

1. The loader starts both queries. Before the route renders, its loader passes prefetch entries for the post and its comments to loadHandles. Both requests go out at once.

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:

src/routes/posts.tsx
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 { function loadHandles<TFeatureFlags extends FeatureFlags, TEntries extends Record<string, AnyTRPCEntry>>(trpc: DecorateRouterKeyable<TFeatureFlags>, entries: TEntries): Promise<TRPCLoadedHandles<TEntries>>loadHandles, function prefetchEntry<TDef extends ResolverDef>(procedure: DecorateQueryProcedure<TDef>, ...args: EntryArgs<TDef>): TRPCQueryEntry<TDef, "prefetch">prefetchEntry, function fromHandle<TDef extends ResolverDef, TData = TDef["output"]>(trpc: DecorateRouterKeyable<NoInfer<TDef["featureFlags"]>>, handle: TRPCQueryHandle<TDef>, opts?: UnusedSkipTokenTRPCQueryOptionsIn<NoInfer<TDef["output"]>, TData, ErrorOf<TDef>, TDef["featureFlags"]>): UnusedSkipTokenTRPCQueryOptionsOut<TDef["output"], TData, ErrorOf<TDef>, TDef["featureFlags"]> (+1 overload)fromHandle } from '@trpc/tanstack-react-query/unstable-handles';
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>, {
    ...;
}>
useTRPC
} from '../lib/trpc';
export const
const Route: Route<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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">, ... 9 more ..., undefined>
Route
=
createFileRoute<"/posts", RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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: TRPCOptionsProxy<AppRouter>; }, ... 8 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: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.loaderDeps?: ((opts: FullSearchSchemaOption<RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc }, deps }: LoaderFnContext<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts", ResolveParams<"/posts">, {
    sort: "new" | "top";
    page: number;
}, AnyContext, AnyContext, AnyContext, unknown, undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefined
loader
: async ({
LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 7 more ..., undefined>.context: {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}
context
: {
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
},
deps: {
    sort: "new" | "top";
    page: number;
}
deps
}) => {
const [
const handles: HandlesOf<{
    posts: TRPCQueryEntry<{
        input: {
            sort: "new" | "top";
            page: number;
        };
        output: {
            id: string;
            title: string;
        }[];
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "prefetch">;
}>
handles
] = await
loadHandles<FeatureFlags, {
    posts: TRPCQueryEntry<{
        input: {
            sort: "new" | "top";
            page: number;
        };
        output: {
            id: string;
            title: string;
        }[];
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "prefetch">;
}>(trpc: DecorateRouterKeyable<FeatureFlags>, entries: {
    posts: TRPCQueryEntry<{
        input: {
            sort: "new" | "top";
            page: number;
        };
        output: {
            id: string;
            title: string;
        }[];
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "prefetch">;
}): Promise<...>
loadHandles
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
, {
posts: TRPCQueryEntry<{
    input: {
        sort: "new" | "top";
        page: number;
    };
    output: {
        id: string;
        title: string;
    }[];
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}, "prefetch">
posts
:
prefetchEntry<{
    input: {
        sort: "new" | "top";
        page: number;
    };
    output: {
        id: string;
        title: string;
    }[];
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>(procedure: DecorateQueryProcedure<{
    input: {
        sort: "new" | "top";
        page: number;
    };
    output: {
        id: string;
        title: string;
    }[];
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, input: {
    sort: "new" | "top";
    page: number;
}, opts?: TRPCEntryOptions | undefined): TRPCQueryEntry<{
    input: {
        sort: "new" | "top";
        page: number;
    };
    output: {
        id: string;
        title: string;
    }[];
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}, "prefetch">
prefetchEntry
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
.
post: DecoratedRouterRecord<{
    ctx: {
        req: Request;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: true;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    byAuthor: QueryProcedure<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    comments: QueryProcedure<...>;
    list: QueryProcedure<...>;
}>, {
    ...;
}> & DecorateRouterKeyable<...>
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
,
deps: {
    sort: "new" | "top";
    page: number;
}
deps
),
}); return
const handles: HandlesOf<{
    posts: TRPCQueryEntry<{
        input: {
            sort: "new" | "top";
            page: number;
        };
        output: {
            id: string;
            title: string;
        }[];
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "prefetch">;
}>
handles
;
}, 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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>, {
    ...;
}>
useTRPC
();
const
const loaderData: {
    posts: 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: TRPCOptionsProxy<AppRouter>;
}, 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">, ... 9 more ..., undefined>
Route
.
RouteExtensions<"/posts", "/posts">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {
    readonly PostsRoute: Route<Register, RootRoute<Register, undefined, {
        queryClient: QueryClient;
        trpc: TRPCOptionsProxy<AppRouter>;
    }, AnyContext, ... 7 more ..., undefined>, ... 15 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
(
fromHandle<{
    input: {
        sort: "new" | "top";
        page: number;
    };
    output: {
        id: string;
        title: string;
    }[];
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}, {
    id: string;
    title: string;
}[]>(trpc: DecorateRouterKeyable<NoInfer<{
    keyPrefix: false;
}>>, handle: 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<...>, {
    keyPrefix: false;
}> | undefined): UnusedSkipTokenTRPCQueryOptionsOut<...> (+1 overload)
fromHandle
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>, {
    ...;
}>
trpc
,
const loaderData: {
    posts: TRPCQueryHandle<{
        input: {
            sort: "new" | "top";
            page: number;
        };
        output: {
            id: string;
            title: string;
        }[];
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
}
loaderData
.
posts: 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.

Keep the sort in
Page 1 of 5
  • Typesafe APIs without codegen▲ 3
  • Streaming responses over HTTP▲ 40
  • Subscriptions with server-sent events▲ 27

What the loader sees

loaderDeps: ({ search }) => search,
// { sort: 'new', page: 1 }
loader: ({ deps }) =>
  loadHandles(trpc, {
    posts: prefetchEntry(trpc.post.list, deps),
  }),

Requests

  1. loaderpost.list({ sort: 'new', page: 1 })done
The URL holds the whole input. Every navigation, the back button and a reload give the loader everything it needs, so the list starts before the page renders.

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.

Escape hatch: loading a dependent query before render (TanStack Router)

beforeLoad runs before the loader, so it can wait for the post and hand its data to the loader through the route context. The loader then starts more by the post's author from post.authorId:

src/routes/posts.$id.tsx
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 ensureInfiniteEntry<TDef extends ResolverDef>(procedure: DecorateInfiniteQueryProcedure<TDef>, input: TDef["input"], opts?: TRPCInfiniteEntryOptions<TDef>): TRPCInfiniteQueryEntry<TDef, "ensure">ensureInfiniteEntry,
  function fetchEntry<TDef extends ResolverDef>(procedure: DecorateQueryProcedure<TDef>, ...args: EntryArgs<TDef>): TRPCQueryEntry<TDef, "fetch">fetchEntry,
  function loadHandles<TFeatureFlags extends FeatureFlags, TEntries extends Record<string, AnyTRPCEntry>>(trpc: DecorateRouterKeyable<TFeatureFlags>, entries: TEntries): Promise<TRPCLoadedHandles<TEntries>>loadHandles,
  function prefetchInfiniteEntry<TDef extends ResolverDef>(procedure: DecorateInfiniteQueryProcedure<TDef>, input: TDef["input"], opts?: TRPCInfiniteEntryOptions<TDef>): TRPCInfiniteQueryEntry<TDef, "prefetch">prefetchInfiniteEntry,
} from '@trpc/tanstack-react-query/unstable-handles';
import { 
function Comments(props: {
    comments: inferInfiniteQueryHandle<TRPC["post"]["comments"]>;
}): JSX.Element
Comments
} from '~/components/comments';
import {
function MoreByAuthor(props: {
    posts: inferInfiniteQueryHandle<TRPC["post"]["byAuthor"]>;
}): JSX.Element
MoreByAuthor
} from '~/components/more-by-author';
import {
function Post(props: {
    post: inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post
} from '~/components/post';
export const
const Route: Route<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id", "/posts/$id", undefined, ResolveParams<"/posts/$id">, AnyContext, AnyContext, ({ context: { trpc }, params }: BeforeLoadContextOptions<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 6 more ..., undefined>) => Promise<...>, ... 6 more ..., undefined>
Route
=
createFileRoute<"/posts/$id", RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, 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: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.beforeLoad?: Constrain<({ context: { trpc }, params }: BeforeLoadContextOptions<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 6 more ..., undefined>) => Promise<...>, (ctx: BeforeLoadContextOptions<...>) => Promise<...>> | undefinedbeforeLoad: async ({
BeforeLoadContextOptions<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 6 more ..., undefined>.context: {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}
context
: {
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
},
params: {
    id: string;
}
params
}) => {
const [
const handles: HandlesOf<{
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "fetch">;
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>
handles
,
const data: DataOf<{
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "fetch">;
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>
data
] = await
loadHandles<FeatureFlags, {
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "fetch">;
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>(trpc: DecorateRouterKeyable<...>, entries: {
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "fetch">;
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}): Promise<...>
loadHandles
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
, {
// Fresh, since the loader builds another query's input from it
post: TRPCQueryEntry<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}, "fetch">
post
:
fetchEntry<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>(procedure: DecorateQueryProcedure<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, input: {
    id: string;
}, opts?: TRPCEntryOptions | undefined): TRPCQueryEntry<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}, "fetch">
fetchEntry
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
.
post: DecoratedRouterRecord<{
    ctx: {
        req: Request;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: true;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    byAuthor: QueryProcedure<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    comments: QueryProcedure<...>;
    list: QueryProcedure<...>;
}>, {
    ...;
}> & DecorateRouterKeyable<...>
post
.
byId: DecorateQueryProcedure<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> & Record<string, never>
byId
, { id: stringid:
params: {
    id: string;
}
params
.id: stringid }),
comments: TRPCInfiniteQueryEntry<{
    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;
    };
}, "prefetch">
comments
:
prefetchInfiniteEntry<{
    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;
    };
}>(procedure: 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;
    };
}>, input: {
    ...;
}, opts?: TRPCInfiniteEntryOptions<...> | undefined): TRPCInfiniteQueryEntry<...>
prefetchInfiniteEntry
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
.
post: DecoratedRouterRecord<{
    ctx: {
        req: Request;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: true;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    byAuthor: QueryProcedure<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    comments: QueryProcedure<...>;
    list: QueryProcedure<...>;
}>, {
    ...;
}> & DecorateRouterKeyable<...>
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
,
{ postId: stringpostId:
params: {
    id: string;
}
params
.id: stringid },
{
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 },
), }); return {
postHandles: HandlesOf<{
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "fetch">;
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>
postHandles
:
const handles: HandlesOf<{
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "fetch">;
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>
handles
,
post: {
    id: string;
    title: string;
    authorId: string;
}
post
:
const data: DataOf<{
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "fetch">;
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>
data
.
post: {
    id: string;
    title: string;
    authorId: string;
}
post
};
},
FilebaseRouteOptionsInterface<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 12 more ..., undefined>.loader?: Constrain<({ context: { trpc, postHandles, post } }: LoaderFnContext<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", ResolveParams<"/posts/$id">, {}, AnyContext, AnyContext, ({ context: { trpc }, params }: BeforeLoadContextOptions<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, ... 8 more ..., undefined>, ... 6 more ..., undefined>) => Promise<...>, unknown, undefined>) => Promise<...>, RouteLoaderFn<...> | RouteLoaderObject<...>> | undefined
loader
: async ({
LoaderFnContext<Register, RootRoute<Register, undefined, { queryClient: QueryClient; trpc: TRPCOptionsProxy<AppRouter>; }, ... 8 more ..., undefined>, ... 7 more ..., undefined>.context: {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
    postHandles: HandlesOf<{
        post: TRPCQueryEntry<{
            input: {
                id: string;
            };
            output: {
                id: string;
                title: string;
                authorId: string;
            };
            transformer: true;
            errorShape: DefaultErrorShape;
            featureFlags: {
                keyPrefix: false;
            };
        }, "fetch">;
        comments: TRPCInfiniteQueryEntry<{
            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;
            };
        }, "prefetch">;
    }>;
    post: {
        id: string;
        title: string;
        authorId: string;
    };
}
context
: {
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
,
postHandles: HandlesOf<{
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "fetch">;
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>
postHandles
,
post: {
    id: string;
    title: string;
    authorId: string;
}
post
} }) => {
// Starts only after beforeLoad has the post const [
const handles: HandlesOf<{
    moreByAuthor: TRPCInfiniteQueryEntry<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "ensure">;
}>
handles
] = await
loadHandles<FeatureFlags, {
    moreByAuthor: TRPCInfiniteQueryEntry<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "ensure">;
}>(trpc: DecorateRouterKeyable<FeatureFlags>, entries: {
    moreByAuthor: TRPCInfiniteQueryEntry<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "ensure">;
}): Promise<...>
loadHandles
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
, {
moreByAuthor: TRPCInfiniteQueryEntry<{
    input: {
        authorId: string;
        cursor?: number | null | undefined;
    };
    output: {
        items: {
            id: string;
            title: string;
        }[];
        nextCursor: number | null;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}, "ensure">
moreByAuthor
:
ensureInfiniteEntry<{
    input: {
        authorId: string;
        cursor?: number | null | undefined;
    };
    output: {
        items: {
            id: string;
            title: string;
        }[];
        nextCursor: number | null;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>(procedure: DecorateInfiniteQueryProcedure<{
    input: {
        authorId: string;
        cursor?: number | null | undefined;
    };
    output: {
        items: {
            id: string;
            title: string;
        }[];
        nextCursor: number | null;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, input: {
    ...;
}, opts?: TRPCInfiniteEntryOptions<...> | undefined): TRPCInfiniteQueryEntry<...>
ensureInfiniteEntry
(
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;
                authorId: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        byAuthor: QueryProcedure<{
            input: {
                authorId: string;
                cursor?: number | null | undefined;
            };
            output: {
                items: {
                    id: string;
                    title: string;
                }[];
                nextCursor: number | null;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
        comments: QueryProcedure<...>;
        list: QueryProcedure<...>;
    }>>;
}>>>
trpc
.
post: DecoratedRouterRecord<{
    ctx: {
        req: Request;
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: true;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    byAuthor: QueryProcedure<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    comments: QueryProcedure<...>;
    list: QueryProcedure<...>;
}>, {
    ...;
}> & DecorateRouterKeyable<...>
post
.
byAuthor: DecorateQueryProcedure<{
    input: {
        authorId: string;
        cursor?: number | null | undefined;
    };
    output: {
        items: {
            id: string;
            title: string;
        }[];
        nextCursor: number | null;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> & DecorateInfiniteQueryProcedure<{
    input: {
        authorId: string;
        cursor?: number | null | undefined;
    };
    output: {
        items: {
            id: string;
            title: string;
        }[];
        nextCursor: number | null;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
byAuthor
,
{ authorId: stringauthorId:
post: {
    id: string;
    title: string;
    authorId: string;
}
post
.authorId: stringauthorId },
{
getNextPageParam: (last: {
    items: {
        id: string;
        title: string;
    }[];
    nextCursor: number | null;
}) => number | null
getNextPageParam
: (
last: {
    items: {
        id: string;
        title: string;
    }[];
    nextCursor: number | null;
}
last
) =>
last: {
    items: {
        id: string;
        title: string;
    }[];
    nextCursor: number | null;
}
last
.nextCursor: number | nullnextCursor },
), }); return { ...
postHandles: HandlesOf<{
    post: TRPCQueryEntry<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "fetch">;
    comments: TRPCInfiniteQueryEntry<{
        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;
        };
    }, "prefetch">;
}>
postHandles
, ...
const handles: HandlesOf<{
    moreByAuthor: TRPCInfiniteQueryEntry<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }, "ensure">;
}>
handles
};
}, UpdatableRouteOptionsExtensions.component?: RouteComponent | undefinedcomponent: function PostPage(): JSX.ElementPostPage, }); function function PostPage(): JSX.ElementPostPage() { const
const loaderData: {
    moreByAuthor: TRPCInfiniteQueryHandle<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
    post: TRPCQueryHandle<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
    comments: TRPCInfiniteQueryHandle<{
        input: {
            postId: string;
            limit?: number | undefined;
            cursor?: number | ... 1 more ... | undefined;
        };
        output: {
            ...;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
}
loaderData
=
const Route: Route<Register, RootRoute<Register, undefined, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/posts/$id", "/posts/$id", "/posts/$id", "/posts/$id", undefined, ResolveParams<"/posts/$id">, AnyContext, AnyContext, ({ context: { trpc }, params }: BeforeLoadContextOptions<Register, RootRoute<Register, undefined, ... 9 more ..., undefined>, ... 6 more ..., undefined>) => Promise<...>, ... 6 more ..., undefined>
Route
.
RouteExtensions<"/posts/$id", "/posts/$id">.useLoaderData: <RouterCore<Route<Register, any, "/", "/", string, "__root__", undefined, {}, {
    queryClient: QueryClient;
    trpc: TRPCOptionsProxy<AppRouter>;
}, AnyContext, AnyContext, {}, undefined, {
    readonly PostsIdRoute: Route<Register, RootRoute<Register, undefined, {
        queryClient: QueryClient;
        trpc: TRPCOptionsProxy<AppRouter>;
    }, AnyContext, ... 7 more ..., undefined>, ... 15 more ..., undefined>;
}, unknown, unknown, unknown, undefined>, "never", false, RouterHistory, Record<...>>, unknown, boolean>(opts?: (UseLoaderDataBaseOptions<...> & OptionalStructuralSharing<...>) | undefined) => {
    ...;
}
useLoaderData
();
return ( <> <
function Post(props: {
    post: inferQueryHandle<TRPC["post"]["byId"]>;
}): JSX.Element
Post
post: TRPCQueryHandle<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
post
={
const loaderData: {
    moreByAuthor: TRPCInfiniteQueryHandle<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
    post: TRPCQueryHandle<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
    comments: TRPCInfiniteQueryHandle<{
        input: {
            postId: string;
            limit?: number | undefined;
            cursor?: number | ... 1 more ... | undefined;
        };
        output: {
            ...;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
}
loaderData
.
post: TRPCQueryHandle<{
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        authorId: string;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
post
} />
<
function Comments(props: {
    comments: inferInfiniteQueryHandle<TRPC["post"]["comments"]>;
}): JSX.Element
Comments
comments: TRPCInfiniteQueryHandle<{
    input: {
        postId: string;
        limit?: number | undefined;
        cursor?: number | ... 1 more ... | undefined;
    };
    output: {
        ...;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
comments
={
const loaderData: {
    moreByAuthor: TRPCInfiniteQueryHandle<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
    post: TRPCQueryHandle<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
    comments: TRPCInfiniteQueryHandle<{
        input: {
            postId: string;
            limit?: number | undefined;
            cursor?: number | ... 1 more ... | undefined;
        };
        output: {
            ...;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
}
loaderData
.
comments: TRPCInfiniteQueryHandle<{
    input: {
        postId: string;
        limit?: number | undefined;
        cursor?: number | ... 1 more ... | undefined;
    };
    output: {
        ...;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
comments
} />
<
function MoreByAuthor(props: {
    posts: inferInfiniteQueryHandle<TRPC["post"]["byAuthor"]>;
}): JSX.Element
MoreByAuthor
posts: TRPCInfiniteQueryHandle<{
    input: {
        authorId: string;
        cursor?: number | null | undefined;
    };
    output: {
        items: {
            id: string;
            title: string;
        }[];
        nextCursor: number | null;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
posts
={
const loaderData: {
    moreByAuthor: TRPCInfiniteQueryHandle<{
        input: {
            authorId: string;
            cursor?: number | null | undefined;
        };
        output: {
            items: {
                id: string;
                title: string;
            }[];
            nextCursor: number | null;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
    post: TRPCQueryHandle<{
        input: {
            id: string;
        };
        output: {
            id: string;
            title: string;
            authorId: string;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
    comments: TRPCInfiniteQueryHandle<{
        input: {
            postId: string;
            limit?: number | undefined;
            cursor?: number | ... 1 more ... | undefined;
        };
        output: {
            ...;
        };
        transformer: true;
        errorShape: DefaultErrorShape;
        featureFlags: {
            keyPrefix: false;
        };
    }>;
}
loaderData
.
moreByAuthor: TRPCInfiniteQueryHandle<{
    input: {
        authorId: string;
        cursor?: number | null | undefined;
    };
    output: {
        items: {
            id: string;
            title: string;
        }[];
        nextCursor: number | null;
    };
    transformer: true;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
moreByAuthor
} />
</> ); }

This serialises the loads: more by the author can't start until the post has arrived. Prefer one loadHandles that starts both from the URL, or read the dependent query in the component, which fetches it in render instead. Reach for beforeLoad only when the dependent data must be in the cache before the page renders.

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 load handles in loaders. In development, tRPC warns when it renders a handle whose data never reached the page's QueryClient.