← All posts

Why our content model is a graph, not a table

Documents are nodes and references are edges. Why a graph beats a table the moment a client asks for the same content somewhere else too.

Most content systems store content as rows: a posts table, a pages table, a products table, each row a bag of fields. It's the obvious model and it's fine for a while. It starts costing you the moment content needs to relate to other content, which is roughly the moment any real client site exists.

We store content as a graph. Documents are nodes, and the relationships between them are edges — real records of their own, not strings hidden in a field. This post is about what that buys and what it costs.

The four primitives

It's worth naming them, because the distinctions do all the work:

A field is one unit of data — a title, a price, a date. It lives inside a document.

A document is a thing with identity. It has an id, it can be queried on its own, it can be published or unpublished, it has a version history. A blog post, a location, a team member.

A stack is a reusable named group of fields that lives inside a document — a hero, a feature grid, an address. No identity of its own, no separate row. A page is composed as an ordered list of these, which is what makes block composition work.

A reference is a link between two documents, stored as an edge with a direction, a type and an order.

The useful question when modelling anything is: does this need its own identity? A team member does — they're linked from three pages, they have their own URL, they get updated once. The layout of a hero doesn't; it belongs to the page it's on. Get that call right and the model stays clean for years.

What first-class references actually buy

Traversal in both directions. An author's posts and a post's author are the same edge read two ways. In a table model, one direction is a column and the other is a scan, so one of them is cheap and the other quietly isn't.

Ordering that means something. Menu items, a curated list of featured case studies, the order of related products. An edge carries an order, so "these three, in this order" is data rather than a convention someone has to maintain.

Metadata on the relationship itself. A person's role on a project isn't a property of the person or of the project — it's a property of the connection. In a table model that ends up as a duplicated field on one side or an awkward join table nobody documents.

Referential honesty. Because a reference is a record, the system knows a page links to that team member. Deleting content that's in use is a question the platform can ask instead of a surprise you find later. Same for assets: the media library can tell you where an image is used.

The reuse problem, which is the real one

Here's the case that decides it. A client has a team page. Then they want the relevant team member on each service page. Then the new microsite wants the same people.

In a flat model, that's copy-paste. Three copies, three places to update, and within a year they disagree — somebody left, one of the three knows, and a decommissioned staff member is smiling out of a service page nobody reads.

In a graph, there's one team-member document and three edges pointing at it. Update the person once and everywhere they appear is correct, because everywhere they appear is the same record.

This is what "the same content, somewhere else too" actually means when a client asks for it. It's almost never a new kind of content. It's the existing content, related differently.

And it's what makes the API good

The generated GraphQL API falls straight out of the model. Each type becomes an object type. A to-one reference becomes a field returning that node. A to-many reference becomes a paginated connection. References resolve in batches rather than one query per item, so a nested query stays a handful of database round trips rather than one per related document.

You didn't write any of that. It exists because the model said author is a reference to an author, and it changes the moment you change the model — no redeploy.

What it costs

You have to think about the model first. "Is this its own thing, or part of this thing?" is a real decision, and it's harder than adding another field. A flat model lets you defer that decision; a graph makes you take it. The deferral is usually what you're paying off later, but it's a genuine cost on day one.

It's more work for genuinely simple sites. A five-page brochure site with no repeated content doesn't need any of this, and setting up types and references for it is ceremony.

Getting it wrong is visible. Model a stack as a document and you've got orphaned records with no page. Model a document as a stack and you'll be copy-pasting it inside six months. Both are fixable, neither is free.

The rule we'd offer

If the thing is edited in one place and shown in several, it's a document with references. If it's edited where it's shown and nowhere else, it belongs inline.

That single question, asked once per content type at the start of a build, is most of what separates a site that ages well from one that needs a rebuild the first time a client says "can we also show these on the new page?"

---

Publishing order: 1, 4, 5 first — one comparison, the principle, the shareable one. Then 2 and 3 to complete the comparison set. Then 7 and 12 for search volume. The rest as they come.

EARLY ACCESS

Not open just yet.

Private build. Self-serve opens shortly — tell us about your agency and we’ll get you in. Already have a workspace? Log in.