Pages, routing and layouts
Files become URLs
| File | URL |
|---|---|
src/pages/index.md |
/ |
src/pages/about.md |
/about/ |
src/pages/blog/index.md |
/blog/ |
src/pages/blog/launch.md |
/blog/launch/ |
src/pages/404.md |
the not-found page |
Files and folders that start with _ are ignored. Two files that would produce the same URL stop the build. Link to pages with a trailing slash (/about/); vage check warns about links that would redirect.
Frontmatter
---
title: Pricing # required
description: Plans and prices. # meta description, Open Graph and llms.txt
draft: true # visible in dev, left out of builds
noindex: true # published, but kept out of search and the sitemap
canonical: /pricing/ # point search engines at another URL
image: /images/pricing.png # social preview image
type: article # adds Article structured data
author: Your Name # only real values
datePublished: 2026-10-23
---Extra keys are allowed, and layouts can read them. This site's home page keeps all of its copy in frontmatter, which makes vagejs its CMS.
Layouts
A layout is a TypeScript file in src/layouts/ that returns HTML:
import { escapeHtml, type Layout } from "vagejs";
const layout: Layout = ({ content, page, site }) => `<!doctype html>
<html lang="${escapeHtml(site.lang)}">
<head><title>${escapeHtml(page.title)}</title></head>
<body><main>${content}</main></body>
</html>`;
export default layout;Pick one with layout: <name> in frontmatter; default is used otherwise. content is already-rendered HTML; escape everything else. vagejs adds the description, canonical URL, Open Graph tags and JSON-LD before </head>, unless your layout already writes them.
HTML in Markdown
HTML inside Markdown is sanitized: scripts, event handlers, javascript: links, iframes and forms are removed. Put embeds in a layout, which is trusted code. If every author is trusted, you can turn this off with markdown: { html: "allow" }.