Free MIT template. If it saved you time, you can tip the maker:
Support on Ko‑fi →Want it built for your own brand? Send a brief, a mockup you already have, or start from scratch:
Ask about custom work →Made by Allan Niñal
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.