Skip to content

Free MIT template · Day 6 of 365

You're looking at a demo. Download the built static files (drop on any host) or grab the full source to customize.

↓ Download static ↓ Source (Astro) All 365 →Build with me →

# createRouter()

## Signature

    function createRouter<T extends RouteDefinition[]>(  routes: T,  options?: RouterOptions,): Router<T>;

## Parameters

### `routes` — required

An array of route definitions. Each route has shape:

    type RouteDefinition = {  path: string;  handler: () => Promise<unknown>;  meta?: Record<string, unknown>;};

Field | Type | Description
--- | --- | ---
`path` | `string` | URL pattern. Supports literals (`/about`), params (`:id`), and a trailing wildcard (`*`).
`handler` | `() => Promise<unknown>` | Called on match. Typically `() => import('./page')`. The resolved value is exposed on `match.handler()`.
`meta` | `Record<string, unknown>` | Optional. Arbitrary data attached to the route — useful for `requiresAuth`, `layout`, page metadata, etc. Available as `match.meta`.

### `options` — optional

    type RouterOptions = {  base?: string;  hash?: boolean;  scrollBehavior?: 'auto' | 'instant' | 'smooth' | 'manual';};

Option | Default | Description
--- | --- | ---
`base` | `'/'` | Base path for all routes. Set to `'/app/'` if your SPA lives at `example.com/app/`.
`hash` | `false` | Use `#`-based URLs instead of HTML5 history. Useful when deploying to environments that can’t be configured to fall back to `index.html`.
`scrollBehavior` | `'auto'` | What to do on navigation. `'manual'` disables routekit scroll handling entirely.

## Returns

A `Router<T>` instance:

    type Router<T> = {  start: () => void;  go: (target: string | NavigateTarget) => void;  replace: (target: string | NavigateTarget) => void;  back: () => void;  forward: () => void;  match: (url: string) => Match | null;  on: <E extends RouterEvent>(event: E, handler: RouterEventHandler<E>) => () => void;  url: string;          // current URL (read-only)  routes: T;            // the route table you passed in};

## Examples

### Minimal

    import { createRouter } from 'routekit';
    const router = createRouter([  { path: '/',           handler: () => import('./pages/home') },  { path: '/posts/:id',  handler: () => import('./pages/post') },]);
    router.start();

### With base path

If your app is served at `https://example.com/docs/`:

    const router = createRouter(routes, { base: '/docs/' });

This is what `PUBLIC_BASE_PATH` in this template’s `astro.config.mjs` configures for Starlight — the same idea.

### With route metadata

    const routes = [  {    path: '/admin',    handler: () => import('./pages/admin'),    meta: { requiresAuth: true, layout: 'admin' },  },] as const;
    const router = createRouter(routes);
    router.on('navigate', ({ match }) => {  if (match.meta?.requiresAuth && !isLoggedIn()) {    router.replace('/login');    return;  }  // ... render});

Note

`meta` is opaque to routekit — it’s just a typed object you can read in handlers. routekit never inspects it.

## Caveats

- Order matters. The first matching route wins. Put specific routes before generic ones. See match order.
- `as const` for type safety. Without it, `routes[N].path` widens to `string` and you lose param inference. See TypeScript usage.
- Single router per app. Creating multiple `createRouter()` calls is supported but rarely useful — they’ll all attach delegated `<a>` interceptors and may double-handle clicks. If you need conditional routing, use route `meta` and branch in your `'navigate'` handler.