The one idea
TanStack Router's whole point: the route DEFINITION is the single source of truth, and types flow OUT of it to every navigation, param read, and loader — no stringly-typed paths.
You define a route once (path params + validated search params + loader return +
context). The router infers all of it and pipes those types through the entire
routing experience. Every <Link>, navigate(),
useParams(), useSearch() and loader is then checked against
that definition at compile time. Contrast: react-router hands you
Record<string, string | undefined> and lets <Link to>
be any string — valid URL or not.
The type-flow — types flow OUT of the route definition
One route definition below. Five touchpoints read from it — and each one is
typed by inference, no angle brackets, no
as any. The generated route tree is what lets the router know all routes
in advance, which is the prerequisite for any of this.
// routes/users.$userId.tsx — file-based routing generates the route tree export const Route = createFileRoute('/users/$userId')({ // search params: validated → typed (standard schema) validateSearch: (search) => ({ page: Number(search.page ?? 1), // page: number filter: String(search.filter ?? ''), // filter: string }), // loader: params + search are typed IN; the return type flows OUT loader: ({ params, search }) => fetchUser(params.userId, search.page), component: UserComponent, })
Type-check simulation — flip a param, watch it fail
A deterministic mock of what the TypeScript compiler does. Pick what the developer
writes into <Link> / navigate(), then run the
type-check. Correct input → PASS;
a wrong param type, a missing search key, or an unknown route → a simulated TS error +
FAIL. The gold-check above pins:
correct → pass, wrong → fail, and exactly 5 touchpoints.
— pick a scenario above —Typed touchpoints — TanStack vs react-router
Same six touchpoints, two routers. react-router's useParams returns
Record<string, string | undefined> and its <Link to>
accepts any string — there's no route tree to check against.