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.