Layout (Wrapper)
A layout is the outer HTML shell every page is wrapped in — the <!DOCTYPE html>, <head>, navigation, and footer that appear on every page of the site. Page templates fill in the body; the layout supplies everything around it. By centralizing the shell in one file, you change site chrome once and the change propagates everywhere.
This page covers where layouts live, how they receive page output, the main variable, the default layout, and how to use multiple layouts when sections of your site need different chrome.
Where Layouts Live
Layouts live under layouts/ as .ejs files. The default layout is common.ejs — every page uses it unless you specify otherwise:
layouts/
common.ejs Default layout — applied to all pages by default
marketing.ejs Optional alternate layout
bare.ejs Optional minimal layout (e.g., for landing pages)
You add layouts simply by creating new .ejs files in the folder. There's no registration step — the filename is the layout key.
The main Variable
A layout receives one thing that page templates don't: the main variable. main is the rendered output of the page template — already-converted HTML, ready to be dropped into the layout's body:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<header>
<!-- ...nav... -->
</header>
<main>
<%- main %>
</main>
<footer>
<!-- ...footer... -->
</footer>
</body>
</html>
main is only meaningful inside a layout template — not inside page, block, or entry templates. Use <%- (unescaped) to output it; it's already HTML.
The same item you'd access in the page template is also available in the layout — both run with the current page record as item. This means the layout can look at the page's metadata, conditionally adjust chrome, or pull in entries based on page fields.
A Practical Layout
A typical layout pulls navigation and footer content from entries, sets baseline head metadata, and includes site-wide assets:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<% link({ rel: 'icon', href: src(getImage('favicon'), '32x32') }) %>
</head>
<body>
<% const header = getEntry('header'); %>
<header>
<a href="/" class="logo">
<%- img(getImage('logo'), '180x40') %>
</a>
<nav>
<% for (const link of header.nav_links) { %>
<a href="<%= link.href %>"><%= link.label %></a>
<% } %>
</nav>
</header>
<main>
<%- main %>
</main>
<% const footer = getEntry('footer'); %>
<footer>
<p><%- footer.copyright %></p>
</footer>
</body>
</html>
Notice that <title> is not set here — page templates set the title themselves via title(), and the layout doesn't override it. The same goes for description, OG tags, and canonical URLs. The layout sets defaults (viewport, charset, favicon); pages set per-page metadata.
→ Template Context and Data Access
Head Injection Across Layout and Page
The head-injection helpers — title(), meta(), link(), style(), script() — collect everything called from any template (layout, page, block, entry) and place it correctly in the final HTML. This means a block template deep inside the page can declare its own stylesheet:
<!-- blocks/code-sample.ejs -->
<% link('/css/syntax.css') %>
<pre class="code-sample"><code><%= item.source %></code></pre>
…and the layout doesn't have to know about it. The CSS link appears in the final <head> once per build, deduplicated, regardless of how many code-sample blocks the page contains.
Use the layout for site-wide assets and metadata. Use page templates for per-page metadata. Use block templates for per-block assets. The injection system reconciles all three.
Choosing a Different Layout
Most pages use common.ejs by default. To use a different layout, set it via a comment at the top of the page template:
<% // layout: marketing.ejs %>
<h1><%= item.title %></h1>
<%- render(item.sections) %>
Or to opt out of a layout entirely (for special outputs like rss.xml, llm.txt, or sitemap.xml):
<%# layout: none %>
<?xml version="1.0" encoding="UTF-8"?>
...
When a page opts out, the template's output is the file's content — no wrapper. This is the right choice for XML, plain-text, and any file where injecting a <!DOCTYPE html> would break the contract.
When to Use Multiple Layouts
A single common.ejs works for most sites. Add additional layouts only when a section of the site truly needs different chrome:
- Marketing landing pages with no nav/footer for distraction-free conversion.
- App-style admin or dashboard views with a different shell than the public site.
- Documentation with a sidebar/TOC layout while the rest of the site uses a centered marketing layout.
For one-off differences (a hero section vs. no hero), don't make a new layout — conditionally render inside common.ejs based on a page field. Layouts are for shape, not variants.
What's Next
- Model Templates — Page, block, and entry templates that fill in
main. - Template Context and Data Access — The full reference for
main,item, helpers, and head injection. - Assets and Static Files — CSS, JS, and other files referenced from the layout.
- Site Builder Architecture — How layout, page, and block templates compose during a build.