Pages & Layouts
Every URL is a page. Layouts wrap page content to eliminate boilerplate. Both support data sources, CSS, JS, and the full template syntax.
Pages
A page maps a URL path pattern to a template. The path field supports URLPattern syntax for dynamic routes:
/ → exact match
/blog/:slug → path parameter, available as {{ params.slug }}
/products/:id/reviews
Fields
| Field | Type | Description |
|---|---|---|
id | TEXT PK | Unique identifier |
path | TEXT UNIQUE | URL path pattern (supports :param wildcards) |
title | TEXT | Page title (used in <title> and {{ title }}) |
layout | TEXT | Layout ID (defaults to default) |
data | TEXT (JSON) | Data sources to resolve |
content | TEXT | Template HTML |
meta | TEXT (JSON) | Key/value pairs for <head> meta tags |
head_html | TEXT | Raw HTML injected before </head> |
body_html | TEXT | Raw HTML injected before </body> |
css / js | TEXT | Page-specific CSS / JS |
validation | TEXT (JSON) | Validation rules for params, query, body |
handler | TEXT | Custom JS handler — bypasses template rendering |
SEO: always set a meta description — {"description":"A concise summary."}. Pages without one fail Lighthouse audits.
Layouts
Layouts wrap page content. The seeded default layout renders the HTML shell, navbar, footer, and a <main> landmark. The page body is inserted where the layout template has {{content}}:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
{{ component name:"navbar" }}
<main id="main-content" tabindex="-1">
{{content}}
</main>
{{ component name:"footer" }}
</body>
</html>
Fields
| Field | Description |
|---|---|
id | Layout identifier referenced by pages (default, docs, ...) |
name | Human-friendly name |
data | Data sources resolved for every page using this layout |
css / js | Injected on every page using this layout |
content | Shell template with the {{content}} placeholder |
Resolution order
When a request matches a page:
- Validation rules run first — failures return 404.
- If the page has a
handler, it takes over the full request/response cycle. - Otherwise, data sources from page + layout + referenced components are collected, deduplicated, and resolved in parallel.
- The page template renders, then injects into the layout at
{{content}}.
Data source priority: when the same key is defined at multiple levels, page wins over layout, layout wins over component.