camelon

File-based routing

Routes live in src/routes/. Folders are URL segments. A file's first character sets its role. There is no route config; the tree is the routing table.

routes/
  _middleware.ts      every route
  _index.tsx          /
  kontakt.tsx         /kontakt
  admin/
    _middleware.ts    /admin/* (runs after the root middleware)
    _index.tsx        /admin
    dashboard.tsx     /admin/dashboard
    $id.tsx           /admin/:id
  detail/
    $.tsx             /detail/* (catch-all)

File roles

Prefix Role
_index the folder's own route
_middleware middleware for this folder and below
_404 not-found renderer for this folder
_error error boundary for this folder
$name dynamic segment → :name
$ catch-all → :splat(*)
a–z… a URL segment
A–Z… a co-located component, not a route

Static routes register before dynamic ones, and longer paths before shorter, so the most specific match wins.

Page routes and resource routes

A route is a page route if it has a default export. It renders HTML. It can also export loader, action, head, and error.

export default function Home() {
  return <h1>Hello</h1>;
}

A route with no default is a resource route. loader handles GET, action handles POST. Use these for Datastar endpoints, JSON APIs, and raw responses.

export async function action({ stream, signals }) {
  const { count = 0 } = signals;
  stream.patchSignals({ count: count + 1 });
}

params

A $name file captures one segment, $ captures the rest. Read them from params.

import type { RouteArgs } from 'camelon';

export function loader({ params }: RouteArgs) {
  return { id: params.id };
}

export default function User({ id }: { id: string }) {
  return <h1 safe>User {id}</h1>;
}
export default function Users() {
  return (
    <ul>
      <li>
        <a href="/users/ada">Ada</a>
      </li>
      <li>
        <a href="/users/lin">Lin</a>
      </li>
    </ul>
  );
}

Next: middleware and errors.