Blog

How the news pipeline fills this site

· pipeline · architecture

The News section of this site updates itself, but there's no database and no CMS behind it. The whole mechanism is: a pipeline appends objects to a JSON file, commits it to the repository, and the host redeploys. This post is about why that boring design is the point.

The contract

Each news channel is one JSON file. An article is an object like this:

{
  "title": "Headline",
  "url": "https://example.com/story",
  "source": "Publisher name",
  "date": "2026-07-01",
  "summary": "One or two sentences.",
  "tags": ["tag-one", "tag-two"]
}

Only title is required; everything else degrades gracefully. Order in the file doesn't matter, because the page sorts by date at render time. That last detail is deliberate: it means the pipeline can append blindly instead of maintaining sorted order, which keeps the write path as dumb as possible. Dumb write paths don't corrupt data.

Why JSON files in a git repo?

  • History for free. Every curation decision is a commit. If the pipeline goes haywire, git revert is the recovery plan.
  • Diffable moderation. I can review what the pipeline picked up the same way I review code.
  • No new infrastructure. The publish step is git push. Cloudflare sees the commit and redeploys the static files. There is nothing to patch, scale, or get breached.

Trust boundaries

One design rule worth calling out: the rendering code treats the feed JSON as untrusted, even though I control the pipeline that writes it. Every string is inserted with textContent rather than innerHTML, and article URLs pass through an allowlist that accepts only http(s), relative, and hash URLs. Pipelines ingest text from the open web; the day some headline contains a script tag, I'd like the answer to be "so what."

The costs

No server means no server-side search, no pagination, and the browser downloads the whole feed to show any of it. At the current scale — dozens of items per channel — none of that matters. If a feed ever grows into the thousands, the fix is archiving old items into yearly files, which is again just moving JSON around in a git repo.

Simple systems have simple failure modes. That's the whole trick.