fastsite is a publishing engine, not a CMS. You send content, it produces fully static HTML, commits it per site, and lets your CDN serve it. Here is the whole model, start to finish.
1. You send bodies, not pages
Content is body-only HTML. You never author a full page — the shell owns the chrome, and the engine wraps every body in it at build time. There are four ways in, and they all land in the same place:
- REST —
POST /api/v1/sites/{site}/contentwith an API key. - MCP — the
create_postandcreate_pagetools, under OAuth consent. - Webhooks — an HMAC-signed payload from an upstream system, mapped to a post, page, media import, or component.
- The dashboard — for when you would rather just type it yourself.
Every one of those paths runs the same sanitizer: a fixed tag allowlist, inline styles stripped, scripts and iframes removed. Problems become warnings attached to the record — they are normalised and published, never turned into a failed request. A bad paste should not be able to wedge your pipeline.
2. The shell owns everything else
The shell is a small tree of Twig templates, a tokens.css design system, and per-locale UI strings. It is the entire design of the site, and it is what an AI agent reads and rewrites over the API or MCP.
Change a token and the whole site re-themes on the next build. Because the design lives in one place and content is only ever a body, there is no template drift between pages and nothing to migrate when the design changes.
Shell writes are guarded — path containment, an extension allowlist, a size cap — and a template that would throw or fail the audit is rejected when you save it, not when the build runs. Snapshot before a risky change; restoring takes its own snapshot first, so the undo is itself undoable.
3. The engine builds static HTML
On every build the engine:
- Expands
<img data-media-id>into responsive<picture>markup — AVIF and WebP at five widths, an original-format fallback, explicit dimensions, and lazy-loading everywhere except the featured image, which is preloaded instead. - Renders components to static markup. React components are server-rendered at build time and the result is HTML — the framework never reaches the browser.
- Inlines minified CSS with tokens first, and ships no JavaScript at all.
- Writes a complete head: title, description, canonical, Open Graph, and JSON-LD.
- Generates
sitemap.xml,robots.txt,llms.txt, cache headers, and redirects — including a redirect appended automatically when a slug changes, so old links keep working.
Builds are incremental. A manifest tracks the hash of every output file, so only what actually changed is written, and files whose source disappeared are deleted rather than left orphaned.
4. The audit decides what may ship
A perfect score is not something you chase after the fact — it is enforced before anything is written. Each build runs a page audit that separates two kinds of problem:
- Failures block the build. A missing image dimension or alt attribute, an external resource, a shipped
<script>, a stylesheet over budget — these are pipeline invariants, and a build that would break one does not complete. - Warnings never block it. A skipped heading level, a heavy page, a non-apex canonical, a link that is not crawlable — these are content smells. They are reported and the page still publishes.
The split is the point. The engine refuses to break its own guarantees, and refuses to hold your content hostage over a style opinion.
The hard goal: 100/100/100/100 on Google PageSpeed for every published page. The audit is what keeps that promise honest.
5. It commits and deploys
Publishing is an explicit step, not a side effect. Writing content stores it; publishing builds the site and pushes it. That means an agent or a script can draft, revise, and stage as much as it likes without anything reaching your visitors.
When a publish does run, the output is committed to your site's git repository — one commit per publish — and pushed. Your static host builds on push. Cloudflare Pages is what this site uses; anything that watches a repo works the same way.
Builds and publishes run on a queue, so the call that triggers one returns immediately and you poll for the result. Failures retry with backoff instead of disappearing, and a burst of edits coalesces into a single build rather than one per change.
Because every publish is a commit, your deployment history is also a rollback mechanism. Nothing dynamic is deployed: no runtime at the edge, no database reachable from the internet, and no origin to fall over under load.
More than one language
Add a locale and the engine translates published content and the shell's UI strings into it, then rebuilds. The primary locale sits at the root and each additional one gets its own subtree, with hreflang alternates and x-default wired up across the whole cluster and a sitemap per language.
Translations are tracked per row against the hash of their source, so editing one paragraph re-translates that page and nothing else. Anything you mark as manually translated is never overwritten. A page that has no translation yet is simply absent from that language — visitors are never served a page that quietly falls back to the wrong one.
Quickstart
- Create a site and design its shell, or start from the default scaffold.
- Create an API key, or connect an agent over MCP.
- Send a post, then publish. Watch a static page appear, committed and live.
- Preview any time without deploying — the preview build is self-contained and needs no server.
- Add a locale and let the engine translate and re-emit the tree.
Ready for specifics? Read the REST API reference, the MCP reference, or the webhooks walkthrough. If you are setting up an engine of your own, start with install.