There are two ways to get content architecture wrong and most projects pick one of them. On one side are the everything-in-Git purists, for whom fixing a typo means a branch, a pull request, and a full deployment. On the other are the people who decide the CMS should hold everything — interface strings, layout config, design tokens, all of it — until the site cannot render a button without a network round-trip. I have built my portfolio and this blog on the same content layer, and the only thing that has held up is a hard boundary between the two.
Early on, everything I built lived in files in the repository — INI, then JSON, all version-controlled and entirely under my hand. It was simple until it wasn't. The day you want to fix a sentence from your phone, or publish a thought without opening an editor, file-only content becomes a wall. Forcing a deployment to move a comma is a waste of a deployment.
So the first time I picked up a headless CMS I overcorrected and put everything in it — UI labels, theme settings, component state, sitting in remote tables next to the actual writing. That trades one failure mode for a worse one. Now the interface chrome arrives over the network, internationalization means an API call, and a CMS hiccup can take down a page that has no business depending on the CMS at all. The answer was never one side or the other. It is a contract between what changes daily and what defines the system.
The CMS gets narrative, mutable content and nothing else — the things that grow and change without anyone needing to refactor. On this blog that is every post, excerpt and topic: I write and publish from an admin screen without touching source. On the portfolio it is the project write-ups, the milestones, the skills; finishing a contract is a content edit, not a release. It is also the right home for the few global settings that need to change without a deploy — an announcement, author metadata, a feature flag. If I might draft it, revise it, or delete it on a whim, it belongs in the database.
Anything that decides how the application behaves, or that needs the compiler to check it, stays in the code. The interface dictionary is the clearest case: both sites are localized into six languages, but the navigation labels, button text, placeholders and aria-labels live in typed JSON in the repo, because fetching them from a CMS would add latency to render the frame and risk a missing key mid-hydration. The same goes for routing rules, theme config, animation timings, validation schemas — they want compile-time checking, not a database row. And the static assets: images, icons and thumbnails belong in the project, where the build can optimize them and the cache can trust them, with no media round-trip in the render path.
Some things fit neither box. Comments, likes, view counts, rate-limit ledgers, sessions — high-frequency, transactional, needing real integrity — do not belong in a CMS built for authoring or in files rebuilt on deploy. Comment threads need replies, auth checks, moderation. A like counter would flatten a static rebuild and crawl through a CMS write API. Rate limits and access logs need a real database doing real-time work. That is a third store, and pretending it is one of the other two is how you end up with a slow, fragile version of both.
Stories go in the CMS. Structure goes in the code. Everything that counts, ticks, or throttles goes in a database that does none of the above.
Architecture is mostly the act of drawing lines and then refusing to move them for convenience. Content and code have different lifecycles; once you stop pretending otherwise, the tools stop fighting you. The CMS lets me publish from anywhere. The repository keeps the parts that have to be right at build time actually right at build time. The boundary between them is the only reason either one is pleasant to use.
The foundation is laid; now it's time to build.