Typed route map
One `as const` map of paths. Params are inferred from the template, so `to()` and `useParams()` can't drift from the route.
For React Router apps without generated route types. Routes live in one map, and each path template gives you the type of its params. A typo in a key or a missing param is a compile error.
The map
export const ROUTES = {
posts: "/blog/posts",
post: "/blog/posts/:postId",
postEdit: "/blog/posts/:postId/edit",
} as const;
type RouteKey = keyof typeof ROUTES;
Params from the template
type ParamsOf<P extends string> =
P extends `${string}:${infer Name}/${infer Rest}` ? Name | ParamsOf<`/${Rest}`>
: P extends `${string}:${infer Name}` ? Name
: never;
type Params<K extends RouteKey> = Record<ParamsOf<(typeof ROUTES)[K]>, string>;
// Params<"postEdit"> → { postId: string }
Helpers
export const to = <K extends RouteKey>(key: K, params: Params<K>) =>
generatePath(ROUTES[key], params);
export const useRouteParams = <K extends RouteKey>(_key: K) =>
useParams() as Params<K>;
Usage
<Route path={ROUTES.postEdit} element={<EditPost />} />
navigate(to("postEdit", { postId: "123" })); // "/blog/posts/123/edit"
to("postEdit", {}); // ✗ missing postId
to("postEdt", { postId: "1" }); // ✗ no such route
const { postId } = useRouteParams("postEdit"); // string
On TanStack Router the generated route tree already gives you this, see Typed route path union.