Typed route path union
Derive RoutePath from the generated route tree — one type alias, zero maintenance, compile-time enforcement.
One type alias derived from the router’s generated output. Every nav config, tab mapping, crosslink, and URL-generating utility is typed against the real route tree. Rename or delete a route and every stale reference becomes a compile error.
The alias
// routePaths.ts
export type RoutePath = FileRouteTypes["to"];
FileRouteTypes is populated by TanStack Router’s code generator via module augmentation on @tanstack/react-router. Run the generator; the type updates. No maintenance required.
The resulting union looks like:
type RoutePath =
| "/orders"
| "/orders/$orderId"
| "/orders/$orderId/returns"
| "/customers/accounts/$customerId"
| "/invoices"
| ... // every registered route
Usage
Use RoutePath anywhere a route path string appears:
// Nav config
type NavItem = { label: string; path: RoutePath };
// Tab route mapping
const TABS: Record<string, RoutePath> = {
overview: "/customers/accounts/$customerId",
orders: "/customers/accounts/$customerId/orders",
};
// Crosslink helpers
function buildLink(to: RoutePath, params: Record<string, string>): string { ... }
When /customers/accounts/$customerId is renamed to /customers/$customerId, every NavItem, tab mapping, and crosslink that referenced the old string fails at tsc, not at runtime.
Exemptions
Fields that point outside the SPA (legacy app routes, third-party URLs) use string explicitly and are annotated:
type NavItem = {
path: RoutePath; // SPA route, enforced
legacyPath: string; // legacy app, exempt
};
Why not just use the router’s <Link to="..."> inference?
<Link to> is enforced at the component boundary. RoutePath enforces the shape in data structures — nav arrays, lookup maps, utility functions — that don’t go through a JSX element. Both are necessary; they cover different surfaces.