Two patterns for generating web pages.
Content Collections (Markdown)
Use for: Blog posts, documentation, articles — many items with similar structure.
| Aspect | Description |
|---|---|
| Data source | Markdown files with YAML frontmatter |
| Storage | Database (for search, analytics) |
| Fields | Fixed set: title, description, author, date, keywords, image |
| Template vars | {{TITLE}}, {{DESCRIPTION}}, {{CONTENT}} |
| Example | Blog posts, docs, legal pages |
---
title: "My Article"
description: "Article description"
kind: "article"
published_at: "2025-01-15"
---
Configured Pages (YAML)
Use for: Homepage, feature pages, landing pages — singleton pages with custom structure.
| Aspect | Description |
|---|---|
| Data source | services/web/config.yaml |
| Storage | None (config file is source of truth) |
| Fields | Unlimited — any structure you define |
| Template vars | {{site.homepage.*}}, {{feature.*}} |
| Example | Homepage, feature pages, pricing page |
homepage:
hero:
title: "Build AI Agents"
subtitle: "Production infrastructure"
features:
- title: "OAuth2 Auth"
description: "Enterprise-ready"
When to Use Which
| Scenario | Pattern |
|---|---|
| 100+ blog posts | Content (markdown) |
| 5 feature pages | Configured (YAML) |
| Legal pages | Content (markdown) |
| Homepage | Configured (YAML) |
| Pricing page | Configured (YAML) |
| Documentation | Content (markdown) |
Rule of thumb: Collections → Content. Singletons → Configured.
Extension Traits
| Trait | When to Implement |
|---|---|
PagePrerenderer |
To define new page types with output paths |
PageDataProvider |
To inject data into existing page types |
Example: Homepage
DefaultHomepagePrerenderer→ defines/index.htmlexistsHomepagePageDataProvider→ providessite.homepage.*data
Example: Feature Pages
impl PagePrerenderer for FeaturePagePrerenderer {
fn page_type(&self) -> &str { "feature" }
async fn prepare(&self, ctx: &PagePrepareContext<'_>) -> Result<Option<PageRenderSpec>> {
// Return one spec per feature page
Ok(Some(PageRenderSpec::new(
"feature",
serde_json::json!({ "feature": feature_data }),
format!("features/{}/index.html", slug),
)))
}
}
Creating a Configured Page
-
Add to config.yaml:
features: pages: - slug: "mcp-native" headline: "MCP Native" highlights: ["Point 1", "Point 2"] -
Create extension with PagePrerenderer: See the Extension Checklist playbook for details.
-
Create template:
services/web/templates/feature.html -
Map template (
templates.yaml):templates: feature: content_types: - feature
Quick Reference
| Pattern | Data | Fields | Use Case |
|---|---|---|---|
| Content | Markdown | Fixed | Collections |
| Configured | YAML | Custom | Singletons |