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.

TypeScript
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:

TypeScript
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:

TypeScript
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.

TypeScript
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.

Start with one command.
You'll have a site running in a minute.

npm create vagejs@latest my-site

Read the docsUse it with your AI assistant