# Templates and layouts

> Write vagejs layouts in TypeScript with the html template tag, list pages with collection(), declare layout fields with Zod, and add structured data.

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.

```ts
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:

```ts
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:

```ts
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.

```ts
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.
