TypeScript usage
routekit is written in TypeScript and ships its own .d.ts files. Every public API is fully typed. The one thing you have to do as a user: use as const on your route table.
The as const dance
const routes = [ { path: '/users/:userId', handler: ... }, { path: '/posts/:slug/comments/:c', handler: ... },] as const; // ← importantWithout as const, TypeScript widens path from the string literal '/users/:userId' to plain string. Once that happens, routekit can’t pull the param names out — they’re gone.
With as const, the types are preserved, and:
type Params = ExtractParams<typeof routes[0]['path']>;// ^ { userId: string }This shape powers the runtime match.params typing.
RouteDefinition
type RouteDefinition = { readonly path: string; readonly handler: () => Promise<unknown>; readonly meta?: Record<string, unknown>;};Routes are readonly so the type system can hold onto literal strings. If you build the table dynamically, the helper defineRoute() keeps the literals in scope:
import { defineRoute } from 'routekit';
const homeRoute = defineRoute('/', () => import('./pages/home'));// ^ inferred as { path: '/'; handler: ...; }You can compose:
const routes = [ homeRoute, defineRoute('/about', () => import('./pages/about')),] as const;Match<T>
router.match() and the 'navigate' event return Match<T> keyed by your route table:
type Match<T> = { path: T[number]['path']; // narrowed to the matched literal url: string; params: ParamsFor<T[number]['path']>; meta: T[number]['meta']; handler: () => Promise<unknown>; state?: Record<string, unknown>;};path is a discriminator. To read params with full type safety, narrow on it:
router.on('navigate', ({ match }) => { if (match.path === '/users/:userId') { match.params.userId; // ← string match.params.foo; // ← compile error }});This is the same pattern as discriminated unions for Redux actions or kind fields on tagged unions.
Generic helpers
import type { RouteDefinition, Match, ParamsFor } from 'routekit';| Type | Purpose |
|---|---|
RouteDefinition | Single route shape. |
Match<Routes> | Router’s match output. |
ParamsFor<Path> | Extract params from a literal path. ParamsFor<'/users/:id'> → { id: string }. |
Router<Routes> | The full router instance type. |
Wildcard typing
Wildcard routes type their captured segment as wildcard: string:
const routes = [ { path: '/files/*', handler: ... },] as const;
router.on('navigate', ({ match }) => { if (match.path === '/files/*') { match.params.wildcard; // ← string (e.g. 'a/b/c.png') }});Strictness
routekit is built with "strict": true and "noUncheckedIndexedAccess": true. If your project uses these too, no surprises. If you’re on looser settings, the types will still work — but you may see fewer compile-time errors than the documentation implies.
Generated types (forward-looking)
The future routekit codegen CLI will write a typed RouteMap so you can do:
import { RouteMap } from './routekit.types';// ^ generated
router.go<'/posts/:id'>({ id: '42' }); // typed call shapeUntil the CLI ships, the as const table + Match<typeof routes> covers ~95% of what people need.