One Codebase, Seventeen Blogs: How the Multi-Site Astro Setup Works


This blog network looks like seventeen websites but deploys from one codebase with zero per-site code. The thesis for builders: environment-driven content loading (one SITE_ID, one glob, one layout set) makes multi-tenancy nearly free — at the cost of build-time scaling linearly with site count.

The three moving parts

First, site resolution: src/lib/site.ts reads SITE_ID from the environment, loads sites/<id>/site.json, and exports the config plus the content path. No database, no CMS — the filesystem is the database, and site.json is the row. Second, content loading: the Astro content collection uses a glob loader pointed at the active site’s content/ folder, so getCollection("blog") transparently returns whichever site’s essays are active. Third, orchestration: scripts/build-all.mjs iterates every folder containing site.json, rebuilding Astro per site into dist/<id>/, plus a directory index build.

Why it works for agent fleets

The contract surface is tiny — site.json schema, Markdown frontmatter, AGENTS.md conventions — so parallel writers (human or model) need no coordination beyond filenames. Builds validate everything: broken frontmatter fails the build instead of shipping. And static output deploys anywhere; here it ships to Cloudflare Pages via the deploy script.

The counterpoint: limits of the design

Linear build scaling already costs ~20 seconds for 17 sites; at 100 sites it becomes minutes, and per-site builds duplicate identical assets. There’s no preview-per-site without env juggling, no shared taxonomies across sites, and filesystem-as-database means no concurrent-write safety — as this week’s disappearing-blog incident demonstrated. Real multi-tenancy eventually wants a CMS layer or at least a lockfile convention.

Takeaway

For fleets of small content sites, this pattern hits a sweet spot: boring technology, total ownership, agent-writable contracts. Outgrow it deliberately — when build minutes or write conflicts hurt — not preemptively.