Typed JSON content beats a CMS

This blog you are reading is two files: a blog.json and a generator script. The docs are the same pattern with a docs.json. There is no CMS, no database, no admin panel, and no markdown parser — and for a developer site, we think that is the strongest version of the idea, not the budget one.

Content as typed blocks

Each post is an object with a slug, title, date, tags, excerpt, and a body that is an array of blocks. A block is a plain object with a kind — no embedded HTML, no markdown strings to parse, just data:

{
  "slug": "my-post",
  "title": "My post",
  "date": "2026-07-07",
  "tags": ["engineering"],
  "excerpt": "One sentence for the index page.",
  "body": [
    { "kind": "p", "text": "A paragraph of plain text." },
    { "kind": "h2", "text": "A section heading" },
    { "kind": "h3", "text": "A subsection" },
    { "kind": "list", "items": ["first", "second"] },
    { "kind": "quote", "text": "A pull quote." },
    { "kind": "code", "text": "pnpm new my-idea --json" },
    { "kind": "video", "src": "../media/demo.mp4",
      "caption": "An optional caption." }
  ]
}

Seven kinds cover everything this site needs. The constraint is the feature: because a paragraph is plain text and a code block is a verbatim string, the generator can escape everything safely, an agent can write posts without ever producing malformed markup, and a schema mistake is a build error rather than a rendering surprise. The video kind is how the demo clips on this site work — a src pointing into the media folder and an optional caption, rendered as a native video element.

What the generator emits

The generator walks the JSON and writes plain HTML pages — but not bare ones. Each page gets an On this page box built from its h2 blocks, with anchor ids emitted to match. Code blocks get a copy button. Posts get prev/next pager cards derived from the array order. And search is real: the build flattens every page's blocks into plain text — list items joined, video captions included — and writes a search-index.json that the client fetches lazily the first time you focus the search box. Client-side search over a static site, no service involved.

Why this beats a CMS here

A CMS earns its complexity when non-technical editors need a UI, drafts, and roles. A developer site has none of those users. What it has is authors who live in git — humans and agents alike — and for them, versioned JSON is strictly better: content changes are diffs you review like code, a post travels through the same PR flow as the feature it documents, blame and revert work, and there is no second system to authenticate against, back up, or migrate. For an agent the difference is even sharper. Writing a post is editing one JSON file with a known schema; there is no CMS API to learn and no WYSIWYG to fight. The whole pipeline is legible in one sitting.

It is also fast in the way only static can be: the output is HTML files served from a CDN, with zero runtime dependencies to fail. The search index is the only JavaScript-adjacent artifact, and it is a static file too.

The regen workflow

Edit blog.json or docs.json, run the site generator, and the HTML, anchors, pager links, and search index are all rebuilt together — they cannot drift from the content because they are derived from it on every build. Deploy is the same one command as everything else here. That loop, edit JSON then regenerate, is the entire editorial process.

Content is data, HTML is a build artifact, and git is the CMS.

If your site's authors are developers and agents, skip the CMS. Type your blocks, commit your JSON, and let a fifty-line generator do the rest.

← All posts