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

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.