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.