Templates and layouts
The HTML around your pages.
A layout is a TypeScript file in src/layouts/ that returns the HTML around a page. Pages choose one with layout: <name> in frontmatter, and default is used otherwise.
import { html, raw, type Layout } from "vagejs";
const layout: Layout = ({ content, page, site }) => html`<!doctype html>
<html lang="${site.lang}">
<head><title>${page.title}</title></head>
<body><main>${raw(content)}</main></body>
</html>`;
export default layout;The layout must contain </head>. vagejs adds the description, canonical URL, Open Graph tags and structured data there, unless your layout already writes them.
What a layout receives
| Prop | What it is |
|---|---|
content |
The page's HTML, already rendered and sanitized. Insert it with raw(content). |
page |
route, title, description, url and the full frontmatter |
site |
Your settings from vage.config.ts |
pages |
Every published page, read-only, for lists and navigation |
Safe templates with html
The html tag escapes every value you insert, so a title like <b>Sale</b> can't break the page or inject code. Arrays are joined, and null, undefined and false render nothing:
html`<ul>${items.map((i) => html`<li>${i.name}</li>`)}</ul>`Use raw() only for HTML you trust, like content. Escaping doesn't check URLs, so if a link comes from frontmatter, allow only paths starting with / and https: links. Plain template strings still work; then wrap every value in escapeHtml().
Lists of pages with collection()
collection() picks pages from pages and sorts them, so a blog index or a "latest posts" box never needs a hand-kept list:
import { collection, html } from "vagejs";
const posts = collection(pages, { under: "/blog/", sort: "date", limit: 5 });
html`<ul>${posts.map((p) => html`<li><a href="${p.route}">${p.title}</a></li>`)}</ul>`;| Option | Values |
|---|---|
under |
A path like "/blog/". The section's own index page is left out unless includeIndex: true. |
sort |
"date" (newest datePublished first), "title", "route" (default) or a compare function |
limit |
The number of pages to return |
Layout fields
A layout can declare the frontmatter fields it accepts. Pages using it are then checked strictly: a misspelled or wrongly typed field stops the build and names the file.
import { z } from "vagejs";
export const frontmatter = z.object({
hero: z.string(),
featured: z.boolean().default(false),
});Defaults are filled in for the page and in pages. A schema can also make built-in fields required, such as author: z.string().
Structured data
vagejs adds WebSite data on the home page and Article data for type: article pages. For more, the helpers website, organization, article, breadcrumbList and jsonLdScript are exported from vagejs. Only describe what's visible on the page.