Skip to content
Talk to an engineer
Architecture

We treat content like code — and the CI proves the words too

Most sites let their copy float free of the system that renders it, so it drifts and rots. Modelling content as typed data puts the words under the same guarantees as the code.

There is a seam in most websites that nobody likes to look at: the one between the code, which is reviewed and typed and tested, and the content, which is a pile of strings edited in a box somewhere and trusted to be right. Code gets a compiler and a test suite. Content gets a hope. That asymmetry is where a surprising amount of rot lives — the stale price, the broken internal link, the heading that quietly went missing in a redesign.

We close that seam by modelling content as typed data — the same shapes, in the same repository, under the same review and the same continuous integration as the code. This note explains what that buys, and the one real thing it costs.

Strings in a box drift; typed data does not

When a page reads its copy from arbitrary strings, nothing connects the words to the structure that is supposed to hold them. A section can lose its heading, a list can empty out, a required call-to-action can vanish, and the page will render anyway — wrong, but without complaint. The failure is invisible precisely because there was never a definition of what "right" was.

A type is that definition, written down and enforced. If a piece of content is declared to have a title, a named author, at least one body section, and a forward link, then a piece missing any of those is not a judgement call in review — it does not compile. The structure becomes a contract, and the contract is checked before anything ships.

type Post = {
  slug: string;
  title: string;
  author: { name: string; role: string };   // a real, named byline — required
  sections: { heading: string; paragraphs: string[] }[];
  related: { title: string; href: string }[];
};
// The same shape the page renders is the shape the CI gate imports as truth.
One typed shape both a static floor and a CMS overlay must satisfy.

One shape, two sources: a static floor and a CMS overlay

Treating content as typed data does not mean hard-coding it forever or denying an editor a friendly interface. The shape is the fixed thing; where a given page’s data comes from is a detail behind a single seam. On this site every template reads through one function that returns the typed shape — and that function resolves to the content-managed entry when one is published, and falls back to a typed, in-repository default when one is not.

That gives us both properties at once: an editor can publish and revise through a real content system, and the engineering still holds a complete, valid, reviewable version of every page in the repository. The CMS is an overlay on a floor that is always there, never a single point of failure the site cannot render without.

The CI reads the same source the page does

Here is the part that turns a nice idea into a guarantee. Because the content is typed data importable as a module, the continuous-integration checks import the exact same source the page renders from, and assert that the built HTML actually contains it — every heading, every byline, every declared link. The expected copy is not transcribed into a test where it can fall out of sync; it is derived from the one source of truth, so the test cannot disagree with the page by construction.

The effect is that a whole class of quiet breakage becomes a loud, blocking failure. Drop a section, empty a list, or let a redesign unmount a heading, and a gate goes red with the exact missing line — before it reaches anyone reading the site.

Dead links become a failing test, not a 404

Internal links are content too, and they are where rot is most expensive — a confident link to a page that no longer exists is worse than no link at all. When the links are typed data rather than free text, the set of real routes is knowable, and a link to a route that is not in it can be rejected mechanically. A dead internal link stops being something a visitor discovers and becomes something the build refuses to ship.

The same machinery enforces the internal-linking strategy itself — that an article links up to the page it supports and across to its siblings — because those relationships are now data that a rule can read, not a convention a writer is trusted to remember.

What you give up

The honest cost is up-front structure. You cannot model content as typed data without first deciding what each kind of content actually is — what a field note requires that a landing page does not, what is mandatory versus optional. That is real work, and it is work you do before the writing rather than after, which can feel slower at the start.

It is the same trade as the performance budget: a little discipline early in exchange for a category of failure that simply cannot happen later. For a site meant to last and grow — more pages, more languages, more editors — paying that structure cost once is much cheaper than paying for the drift forever.

Want to look under the hood?

This is the kind of decision we make on every build. If you are evaluating us for one, talk to the engineer who would architect it — no sales gatekeeper, a reply within one business day, and you own everything we build.