
Type-Safe Routing: No More Guessing Params
If a route's params and search schema are typed, a broken link becomes a compile error instead of a blank page someone finds in production.
Routing is the last untyped seam in most TypeScript React apps. Everything else is checked, and then a link is a string:
<Link to={`/projects/${project.slug}/settings`}>Settings</Link>
Rename that route and nothing complains. The link ships, and it is found by a user.
Routes as typed objects
const projectRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/projects/$slug',
component: ProjectPage,
})
<Link to="/projects/$slug" params={{ slug: project.slug }}>Settings</Link>
Now the path is checked against the route tree and params is checked against that path. A rename is a type error at every call site — which is the entire point of having types.
Reading them is typed too:
const { slug } = projectRoute.useParams() // string, not string | undefined
Search params get a schema
This is the part I did not expect to care about and now will not go without:
const searchSchema = z.object({
page: z.number().int().min(1).catch(1),
sort: z.enum(['recent', 'name']).catch('recent'),
})
const listRoute = createRoute({
path: '/projects',
validateSearch: searchSchema,
component: ProjectList,
})
?page=banana becomes page: 1 instead of NaN propagating into a query and producing an empty screen. Query strings are user input — they arrive from bookmarks, old links, and people editing the address bar — and this is the only routing layer I have used that treats them that way by default.
Loaders remove the waterfall
const projectRoute = createRoute({
path: '/projects/$slug',
loader: ({ params }) => queryClient.ensureQueryData(projectQuery(params.slug)),
component: ProjectPage,
})
Data fetching starts with navigation rather than after the component mounts. The default alternative — render, effect, fetch, spinner — is a waterfall you pay for on every route change, and it is why a lot of SPAs feel slower than the server-rendered pages they replaced.
The honest trade
Setup is heavier than a router where a route is a path and a component. On a five-page marketing site that overhead is not repaid. On an application with dozens of routes, nested layouts and search-driven views, it converts a category of runtime bug into compile errors, and that trade is easy.
Resources
- Repo: TanStack/router
- Docs: tanstack.com/router
- Video walkthroughs: YouTube: tanstack router react tutorial
- Related: Put filter state in the URL, not in useState
Need this built properly?
I build secure, fast, bilingual platforms for clients across Egypt, Saudi Arabia, the UAE and Kuwait.


