Concepts
The two halves of 9d9: static hosting and headless CMS. How they fit together.
9d9 has two halves. The static-site host is the boring one — you build with any framework, push the files, we serve them. The headless CMS is the interesting one — it's the place to keep content that changes between builds (pages, navigation, brand) so your AI agent (or you) can edit it without redeploying.
Static hosting
Every deploy is an atomic snapshot of a dist/ directory.
Files live in R2 under a deploy_id prefix. A small KV entry
per hostname points at the current deploy_id. When you
activate a deploy we flip that pointer; old files stay around for a week
so rollback is one tool call.
Request path
- Browser hits
your-slug.9d9.dev/about. - Render Worker reads
host:your-slug.9d9.devfrom KV — gets{org_id, current_deploy_id}. - Tries R2 keys in order:
about/index.html,about.html, thenabout. - First hit is served. If none, returns the deploy's
404.html(or a generic).
Cache headers
| File class | cache-control |
|---|---|
HTML (*.html, /) | public, max-age=0, must-revalidate + ETag |
Hashed (/_astro/, /_next/static/, *.HASH.*) | public, max-age=31536000, immutable |
| Everything else | public, max-age=300, stale-while-revalidate=86400 |
Headless CMS
Your built site is static, but most sites have some content that changes more often than your codebase deploys. A page edit. A nav reorder. You shouldn't have to push a new build to fix a typo.
The CMS API is read-mostly from your site's perspective — you fetch content at build time, runtime, or both, and render it however you want. Writes happen from the dashboard, the CLI, the MCP server, or directly via REST with a Bearer token.
Content types
- Pages — slug, title, markdown body, structured blocks, optional
pub_date+tagsfor time-ordered content (blog, changelog, news). - Navigation — a primary nav list and a footer nav list. Strings + URLs.
- Brand — name, tagline, palette, fonts, logos, social, address.
- Media — images and other assets, with on-the-fly resize.
Read endpoints (no auth)
Your static site fetches these. They're CORS * and edge-cached for 60s.
GET /v1/sites/{org_id}/public/pages
GET /v1/sites/{org_id}/public/pages/{slug}
GET /v1/sites/{org_id}/public/navigation
GET /v1/sites/{org_id}/public/brand Two flows for "deploy"
| What changed | Where to push |
|---|---|
| Markup, styles, JS, build pipeline | 9d9 deploy ./dist |
| A typo on a page, a nav reorder, a new blog entry | Dashboard or CMS API — no rebuild required |
If your site fetches at build time, content changes need a rebuild + deploy. If it fetches at runtime, content changes are instant. Both patterns work — pick whichever fits the freshness budget.