OMAR
Field NotesCV
Type-Safe Routing: No More Guessing Params
← All Notes
Frontend08 August 2026 · 3 min read

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

ReactTypeScriptTanStack

Need this built properly?

I build secure, fast, bilingual platforms for clients across Egypt, Saudi Arabia, the UAE and Kuwait.

Keep Reading