Routing & Paths
A page's path is the URL it answers. It is matched literally unless it contains a :param or * segment, in which case it becomes a pattern.
Path Forms
| Form | Example | Matches |
|---|---|---|
| Root | / |
The home page. |
| Static | /about |
That exact path, and nothing else. |
| Nested | /settings/billing |
Any number of static segments. |
| Dynamic | /blog/:slug |
One segment, captured as slug. |
| Catch-all | /docs/:rest* |
The remainder of the path, captured as rest — must be the last segment. |
| Wildcard | /files/* |
Anything below /files/, captured under no name. |
A path must match ^/[a-z0-9-_/:*]*$: it starts with /, and only lowercase letters are allowed. /users/:userId is rejected — write /users/:userid, or better, /users/:id.
A path with no : and no * is compared as an exact string. No prefix matching, no trailing-slash tolerance. /about does not answer /about/.
Dynamic Segments
A :param segment captures exactly one path segment — it never spans a /. /blog/:slug matches /blog/hello but not /blog/2026/hello; use /blog/:slug* for the latter.
Dynamic routes are usually paired with a collection block, so one page definition generates a route per record:
pages:
- name: Blog Post
path: /blog/:slug
collection: { table: posts, slugField: slug }
components:
- { type: text, tag: h1, content: '$record.title' }
See Collection & Markdown Pages for the collection contract.
Record Detail Routes
To serve one record at a URL without generating a route per record, pair a dynamic segment with a single-mode data source. param names the path segment to read the record id from:
pages:
- name: Task
path: /tasks/:id
dataSource:
table: tasks
mode: single
param: id
components:
- { type: text, tag: h1, content: '$record.title' }
- { type: text, content: '$record.description' }
A request whose parameter matches no record returns a 404.
$id is not a path syntax. $ is not a legal character in a path, so /tasks/$id fails validation. The dynamic-segment marker is : everywhere; $record.* is a content reference, not a route one.
The Language Segment
Sovrium does not prefix your paths for you. When app.languages is configured, the runtime reads the first path segment, and if it matches a configured language code it becomes the active language for $t: translation lookups.
Localized routes are therefore authored explicitly — one page per language, each carrying its own segment:
pages:
- { name: home-en, path: /en, components: [{ type: hero, content: 'hero.title' }] }
- { name: home-fr, path: /fr, components: [{ type: hero, content: 'hero.title' }] }
Resolution Order
An incoming request is answered by the first of these that matches:
- A real file in the public directory.
- A redirect rule.
- A page path.
- The 404 catch-all.
Related Pages
- Pages Overview — the full page property table.
- Collection & Markdown Pages — one route per record.
- Data Binding —
mode: singleandparam. - Redirects — retiring a path without breaking links.
- Languages — configuring language codes and
$t:keys.
Last updated July 27, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta — contributions and corrections are welcome.