Solve one concrete task
Generate non-HTML routes
Emit XML, JSON, and plain-text outputs without treating them as special cases.
Generate XML, JSON, and text outputs
Non-HTML outputs are first-class routes. Use them for things like:
/sitemap.xml/robots.txt/_headersJSON indexes and feeds
Choose the right route API
Use page(...) when the route path is already known:
.page("/sitemap.xml", |ctx| {
let mut xml = String::from(r#"<?xml version="1.0" encoding="UTF-8"?><sitemapindex>"#);
for route in ctx.routes() {
if route.has_attribute("family", "sitemap") {
xml.push_str("<sitemap><loc>");
xml.push_str(&route.path);
xml.push_str("</loc></sitemap>");
}
}
xml.push_str("</sitemapindex>");
Ok(Artifact::Xml(xml))
})
Use file(...) when the output should be a literal flat file rather than a page-like route:
.file("/robots.txt", |_| {
Ok(Artifact::Text(
"User-agent: *\nAllow: /\nSitemap: /sitemap.xml\n".to_string(),
))
})
Use generated_routes(...) when content decides how many non-HTML outputs should exist:
.generated_routes(
|_ctx| {
Ok(vec![
GeneratedRoute::file(
"/_headers",
"/*\n X-Frame-Options: DENY\n".to_string(),
)
.with_attribute("kind", "hosting-config"),
])
},
|_ctx, text| Ok(Artifact::Text(text.clone())),
)
Pick the matching artifact
Use the artifact that matches the semantics of the bytes you are generating:
Artifact::HtmlArtifact::XmlArtifact::JsonArtifact::Text
Artifact kind and filename are intentionally independent. A text artifact can be written to events.log, and a JSON artifact can be written to an extensionless API file if you register it through file(...).
Decide the page output policy separately
Page-like routes can emit either:
/slug/index.html/slug.html
That policy belongs to the site configuration, not to the artifact type. Explicit flat files always keep the literal output path you registered.