A guide for AI agents (and the people directing them) creating or maintaining sites on the lazysite engine. It captures the separation-of-concerns model the engine is built around, and the failure modes that show up when an agent treats site-building like generating a single web page.
This is the "how to think about a lazysite site" briefing. For the mechanics see its siblings: content authoring, layouts and themes, publishing over WebDAV / the control API, and configuration.
lazysite is built on a strict separation. Keep it.
| Layer | What it is | Where it lives | Who owns it |
|---|---|---|---|
| Content | The words and structure of a page | Markdown .md (or .url) files in the docroot |
Authors (human or AI), non-technical |
| Layout | The HTML chrome: header, nav, footer, page scaffolding, reusable features | layout.tt (+ components/*.tt) |
Site builder |
| Theme | Colours, fonts, spacing, assets - expressed as design tokens | theme.json -> CSS custom properties, main.css |
Designer |
The engine renders content through a layout, styled by a theme, and caches the result. Three sanity checks tell you whether you have kept them separate:
If any answer is "no", you have fused layers that should be apart.
The most common mistake an AI makes is to generate one big HTML file with the markup, all the CSS, and the content fused together - a "web page", not a site.
united.explore.lazysite.io/lazysite-assets/summit-of-summits/ - a single page
built by an assistant:
<style>.<div>s, 10 <section>s of bespoke structure.var(--theme-*) refs - so it
half-knew about tokens, then hardcoded anyway)./lazysite-assets/… - the static-assets path. It bypasses
the rendering engine entirely: no layout.tt, no site nav, no theme system,
no cache pipeline, not registered in sitemap/llms/feeds.main.css.llms.txt entry - it is a dead end in the site.That page is a look (a distinctive editorial theme) plus content. So:
summit): its palette, fonts and
spacing as tokens in theme.json; shared CSS in main.css.Result: the same design, but reusable across a whole site, themeable, authorable, cached once, and on the rails.
The monolith almost always traces back to one front-matter switch: api: true
(equivalently raw: true). That is raw mode - the engine serves the file's
body verbatim and skips the layout and the theme. A page built that way has to
carry its own header, nav, footer and every line of CSS, because the shared
chrome and the design tokens are never applied to it. Put ordinary pages in raw
mode and you are hand-manufacturing monoliths, one per page.
Raw mode exists for a genuinely self-contained artifact - a single bespoke interactive widget or an embed fragment with its own content type. It is not for content. For a normal page, write plain Markdown (title/subtitle front matter, a prose body) and let the engine wrap it in the shared layout + theme.
Most of the "mechanical" busywork that raw-mode publishing seems to demand is a symptom that disappears the moment a page is Markdown:
img/ paths - unnecessary. In Markdown you
write !alt and upload the image once; images are only ever
"embedded" because something authored a self-contained HTML blob.preview_page (server-side
render) or a plain GET of the page URL. Chunked source re-reads are a
workaround for a problem the approach created..md - front matter, words, and . Upload the
page (and any new image), clear the cache, verify with a rendered GET. Seconds,
not surgery.The scaling maths is the punchline. Monolith pattern: every page re-ships its own copy of the design (tens of KB), every edit is big-file surgery, and a restyle means touching every page - cost grows with the page count. Theme + layout pattern: the design is defined once (O(1)), each page is a few KB of content, and restyling the whole site is a single theme swap. As a site - or an estate of sites - scales, those two curves diverge hard: one gets slower and heavier per page, the other stays flat.
.md, wrapped by the engine in the shared layout + theme. Do not set
api: true / raw: true on a content page: that is raw mode, which serves the
body verbatim with no layout and no theme, and is the usual source of monolith
pages. Reserve raw mode for a genuinely self-contained artifact. If you are
writing paragraphs of HTML in a page, stop - it belongs in the layout.layout.tt. Build them once; every page inherits them.var(--theme-*); never hardcode a
colour or font in a page. A theme swap must be able to restyle everything.FOREACH loop (or a components/*.tt component), so reordering can't break
the markup and non-technical people can edit the data. Hand-written repeated
<div>s are a smell.tt_page_var), so any page can opt in - don't bolt bespoke markup into a
single page./lazysite-assets/ (or any static path) as a "page". Assets are for images,
video, fonts, main.css - not documents.<style>
is growing past a few lines, it wants to be in the theme.sitemap.xml / llms.txt, emit
feeds where relevant. Server-rendered semantic HTML is what makes lazysite
sites findable by search and AI.lazysite-agent/<your-partner-id> (e.g.
--user-agent "lazysite-agent/claude-dhcf"). The visitor-stats classifier
treats that token - and the legacy claude-code-agent - as tooling, not a
human visitor, so your QA hits stay out of the audience numbers.api: true / raw: true, with its own content_type) -
never an ordinary content page.When you meet a page like summit-of-summits:
theme.json tokens;
move shared rules into the theme's main.css. Replace every hardcoded hex
with a var(--theme-*)..md that selects the
layout+theme and carries the words; register it for sitemap/llms/feeds.Done once, the design is now reusable, themeable and authorable - and the next page in that style costs almost nothing.
json: data ASCII - the data loader drops non-ASCII (use - not em
dashes, straight quotes, -> not arrows).<style> must be a single line - a leading # on its own line is
parsed as a Markdown heading and mangles the CSS.<strong> inside a
<div>, not **bold**.lazysite-agent/<partner-id> UA (see rule 10) so QA
traffic stays out of the human analytics./lazysite-assets/ or other static paths.api: true / raw: true) - only true self-contained artifacts do.var(--theme-*).