LLumen
← All articles

Designing a content model that survives change

Cover — content-model-that-survives-change

A content model is an API contract with your future self. Every shortcut you take while modelling — a stringly-typed status field, a rich-text blob doing a structured field's job — becomes a migration you'll be writing a year from now, live, against production content.

This site's model is deliberately small: a blogPost and an author. But the way it grew is the interesting part, because it grew the way real models grow: editorial features arrived after launch.

Start from the queries, not the data

The classic mistake is to model whatever shape the source data already has. Model the questions your frontend asks instead: list pages need a summary object; detail pages need the full body; both need stable identity. That's why the slug is a first-class validated field here and not something derived at render time.

  • Fields the frontend never queries are candidates for deletion, not migration.

  • Anything that gates rendering (slug, published date) deserves validation at the model level.

  • Cross-field rules the model can't express belong in editor tooling — not in editors' memory.

Let migrations carry the history

Every change to this model lives in a numbered migration file. 000-create-content-types builds the base model; 001-add-editorial-fields layers on reading time, sponsorship and related posts. A reviewer can read the model's whole biography in the diff.

If a field's meaning isn't obvious from the model alone, the model is wrong — no matter how good the documentation is.


The payoff shows up later: when a webhook needs to write estimatedReadingTime, it finds a disabled integer field waiting for it — added by a migration, owned by automation, invisible to editors. Nothing about that required touching the frontend.