Skip to content

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

SegmentExampleNotes
Literal/aboutExact match.
Parameter/users/:userIdCaptures 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.