edwardcho.dev

Blog Post

Modeling Revision History: Treating Content as Something That Evolves

Why I chose a revision-based model for my personal site, and what it changed about the way the platform works.

Published 3/28/2026

One of the most interesting parts of building my personal site hasn’t been the UI.

It’s been revision history.

At first, it seems like a simple decision: store a document, allow edits, and save changes.

But once you think about content as something that evolves over time, not something that simply gets overwritten, the problem becomes more interesting.

The default approach: overwrite the current state

The simplest way to model content is:

  • one row per document
  • update it in place on every edit

This works well for many cases. It’s straightforward, easy to query, and keeps the system simple.

But it also means:

  • you lose history unless you explicitly track it
  • reverting changes becomes harder
  • understanding how content evolved over time is not built into the model

For a static portfolio site, that might be enough.

For something closer to a content workflow, it felt limiting.

Shifting to a revision-based model

Instead of treating a document as a single mutable record, I started treating it as a revision-based model.

In practice, that means:

  • each edit creates a new revision
  • revisions are append-only
  • past revisions are never modified
  • the document points to the latest revision as its current state

That introduces a little more complexity, but it changes what the system can support:

  • history is preserved by default
  • diffing between revisions becomes possible
  • reverting becomes a matter of changing which revision is current
  • the model reflects how content actually evolves, not just its latest state

The tradeoff is that you introduce more records and a bit more indirection when reading current content. But the clarity is worth it. Append-only revisions make the timeline easier to reason about, reduce the risk of losing history, and create a better foundation for workflow-oriented features later.

The role of the “latest revision” pointer

One challenge with a revision-based model is efficiency.

If every document has many revisions, you don’t want to scan all of them just to render the current state.

The solution is to maintain a pointer from the document to its latest revision.

That gives you:

  • fast reads for current content
  • preserved history without extra work
  • clear distinction between current state (latest revision) and history (revisions)

This pattern ended up being simple but powerful.

Where to enforce correctness

Another question that came up was where to enforce rules.

For example:

  • who is allowed to create a revision?
  • can a user modify someone else’s document?
  • what guarantees that revisions remain immutable?

The UI should still guide the workflow and make valid actions clear. But security, ownership, and core invariants cannot rely on the UI alone.

So I pushed those guarantees into the API and data layer:

  • ownership is validated server-side
  • revisions are created through controlled paths
  • immutability is enforced by design, not convention

That makes the system more predictable and reduces the chance of subtle bugs.

What changed because of this

What surprised me most is how much this decision influenced the rest of the system.

Once content is modeled as a revision-based model:

  • features like diffing and revert become natural extensions
  • the UI can stay simple because the data model carries more meaning

It also changes how you think about future features. Instead of asking “how do I add this?”, you start asking “does the model already support this?”

Closing thought

For a small personal project, it would have been easy to treat content as something that just gets updated in place.

But modeling it as something that evolves over time made the system more interesting and more aligned with real-world workflows.

It’s a good reminder that sometimes the most valuable decisions aren’t about adding features, but about choosing the right shape for the data underneath them.