Routing
The server resolves a request path to a template through a small, declarative route table, with dynamic segments captured as params. Routing is pure-Rust and needs no build step.
Declaring routes
Routes live in backend/routes.json — a list of { path, template } entries:
{
"routes": [
{ "path": "/", "template": "index" },
{ "path": "/posts/:id", "template": "post" },
{ "path": "/files/*path", "template": "file" }
]
}
Each template names a frontend/<template>.html file. The file is optional: without routes.json, the server falls back to its legacy behaviour (a template named after the last path segment), so existing projects are unaffected.
Pattern segments
- static —
posts— must match the path segment exactly. :param— captures exactly one segment by name.*wildcard— captures the rest of the path (only meaningful as the final
segment).
When several patterns match one path, the most specific wins: static beats :param beats *wildcard. So /posts/new matches a literal /posts/new route even when /posts/:id also exists.
Using params in a template
Captured params arrive in the render context under params, so a template can read them directly. For the route /hello/:name:
<h1>Hello, {{ params.name }}</h1>
Values rendered with {{ }} are HTML-escaped automatically. There is a live demo at /hello/world.
Unmatched paths
A request that matches no route renders the index template — the SPA fallback, so client-side routing can take over from there.
Building on routes
Per-route data loaders (a route names a JSON file whose contents feed the template) and layouts (a shared shell template wrapping a page) build on this table — both ship today. See Data loaders and Layouts.