Skip to content

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; // ← important

Without 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';
TypePurpose
RouteDefinitionSingle 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 shape

Until the CLI ships, the as const table + Match<typeof routes> covers ~95% of what people need.