Basic routing
A route is a path plus a handler. routekit supports three kinds of segments — literal, parameter, and wildcard — and matches them top-down through the route table.
Path syntax
| Segment | Example | Notes |
|---|---|---|
| Literal | /about | Exact match. |
| Parameter | /users/:userId | Captures one segment into params.userId. |
| Wildcard | /files/* | Captures everything to the end into params.wildcard. |
const routes = [ { path: '/', handler: () => import('./pages/home') }, { path: '/about', handler: () => import('./pages/about') }, { path: '/posts/:slug', handler: () => import('./pages/post') }, { path: '/posts/:slug/edit', handler: () => import('./pages/edit') }, { path: '/files/*', handler: () => import('./pages/file') },] as const;Params
Each :name segment becomes a string in match.params. Multiple params per route are fine:
{ path: '/orgs/:orgId/teams/:teamId', handler: () => import('./pages/team') }Params are always strings. If you need a number, parse it in your handler:
const userId = Number(match.params.userId);if (Number.isNaN(userId)) { // → not a number; render 404 or redirect}Match order
The route table is walked top-to-bottom. The first match wins. This means specific routes go before generic ones:
// ✅ Correct: '/posts/new' is checked before '/posts/:id'const routes = [ { path: '/posts/new', handler: ... }, { path: '/posts/:id', handler: ... },];
// ❌ Wrong: '/posts/new' will match '/posts/:id' with id='new'const routes = [ { path: '/posts/:id', handler: ... }, { path: '/posts/new', handler: ... },];If you find yourself depending on subtle ordering, that’s a smell — see if you can disambiguate by URL shape (e.g. /posts/new → /posts/create).
Wildcards
A trailing * captures everything else:
{ path: '/files/*', handler: ... }// matches /files/a, /files/a/b, /files/a/b/c.png// match.params.wildcard === 'a/b/c.png'Wildcards are useful for file browsers, catch-all 404 pages, and proxy-style routes. They cannot appear in the middle of a path (/files/*/raw is invalid).
404 handling
Add a wildcard route at the end of your table for unmatched URLs:
const routes = [ // ... real routes ... { path: '/*', handler: () => import('./pages/404') },] as const;Without a fallback, an unmatched URL emits a 'no-match' event instead of 'navigate'. If you prefer, listen for that explicitly:
router.on('no-match', ({ url }) => { console.warn('No route matched', url);});Nested routes
routekit doesn’t have nested-route primitives. Layouts are something you compose at the renderer layer — for example, a React app might have an <AppShell> that always wraps the matched page component. Keep the routing flat and the layout in your renderer.
If you specifically want a nested-router feel (parent owns layout, children render in an outlet), see code splitting for one common pattern.