The HTML file IS the CMS
The deliverable for most websites is HTML. Most CMSs treat HTML as a build output of something else, markdown, MDX, structured content trees, schemaful databases.
Freedom CMS, the tool I built and use to edit this site, treats the HTML file as the source of truth and edits it directly. You click an element, you edit it in place, two seconds later the change is committed to git and pushed. That's the whole interaction.
I built this for myself, but the shape, file-as-source-of-truth, git-as-persistence, no transformation layer, happens to be the shape AI agents work best in. I work with a fleet of agents as coworkers; they edit the same site I do. The fact that the same file is the truth for both of us means we never disagree about what's deployed, and there's no API for the agent to learn beyond filesystem and git. That property turned out to matter more than the contenteditable editor itself.
This article is about why I keep ending up at this shape, and what I had to give up to get there.
The CMS layers I keep removing
The first website I ever built had its content in WordPress's MySQL database. The HTML you saw was rendered on every request from rows in wp_posts. Editing meant logging into /wp-admin/, finding the post, editing it in TinyMCE, pressing Update. The content lived in the database. The HTML was a build artifact, regenerated on demand.
Years later I switched to static site generators. The content lived in markdown files. The HTML was a build artifact, generated by hugo build or astro build or eleventy. Editing meant editing the markdown, running the build, deploying.
Then headless CMSs (Sanity, Contentful, Tina). The content lived in a hosted structured database. The HTML was a build artifact, regenerated when content changed. Editing meant a sleek admin UI, but the underlying shape was the same: content in one place, HTML produced from it elsewhere.
Each of these stacks has the same architecture: source content + build pipeline = HTML. The categories of source content change (rows, markdown, JSON), but the build pipeline is always there, and the HTML is always downstream of something else.
The thing I noticed, building maybe forty client sites with these stacks: most edits I make are edits TO HTML. The text on a page. A link. A heading. The image src. The reason I'm in the CMS at all is to change a string that ends up in the HTML.
What if the HTML file was where I edited, directly?
contenteditable as the editing primitive
The browser has shipped contenteditable since 2007. Set contenteditable="true" on any element and the user can type into it as if it were a textarea, except it preserves the surrounding HTML structure.
Most CMSs use contenteditable inside a sandboxed iframe to power their TinyMCE-or-equivalent editor on a synthetic representation of the page. Freedom CMS uses contenteditable directly on the actual page. You see the live page in your browser. You add ?edit=1 to the URL, every text element gets contenteditable, and you click and type to change content.
Images are clickable; clicking opens a file picker, the file gets uploaded to _assets/ and the <img src> is updated. Links are clickable; clicking opens a small inline panel where you change the href. Sections can be moved up or down with keyboard shortcuts. The view in your browser IS the page IS the editor.
The HTML file on disk is updated as you edit. You don't save; the save is implicit, debounced to avoid thrashing the disk.
The mental model is: the file is the truth. The browser is just a viewer that happens to support editing.
Git as the persistence layer
Once the HTML file on disk is the canonical content, you don't need a database. You need version control.
Freedom CMS commits every save to git, with a message like edit: index.html (felix, contact section). The push is debounced separately, you might make twenty edits in a session and they accumulate into one or two pushes. Git is the database. The commit log is the audit trail. The diff is the change preview.
Cloudflare Pages, hooked to the repo, picks up the push and deploys the new HTML. The deploy IS the file being live. There is no build step, no transform, no template render. The file you edited is the file the browser fetches.
If I want to revert an edit, git revert. If I want to see who changed what when, git log. If I want to see the diff between Tuesday and today, git diff. None of these are CMS features I had to build. They are properties of git, exposed because the storage primitive is git.
The implementation
Freedom CMS is a Rust HTTP server that does three things.
It serves the static site. Like python -m http.server, but with one extra route at /__edit/<path> that returns the same HTML wrapped in an editor shell.
It accepts edit events. The editor shell ships a small JavaScript layer that watches contenteditable changes and posts them to /__edit/<path>/save with a JSON body describing what changed. The server applies the change to the HTML file on disk.
It commits and pushes. After each save, a debounced background task runs git add, git commit -m "<auto>", and git push origin main. If the push fails (network, conflict), it retries; if it keeps failing, it logs and surfaces the error in the editor UI.
That's about 2,800 lines of Rust. The bulk of it is the HTML parser (I wrote a small one rather than depending on a heavyweight crate, because the editing operations are simple) and the file-watch logic. The git interaction is std::process::Command::new("git") because the system git binary is fine.
The editor JavaScript is around 600 lines, no framework. The server has zero dependencies on a frontend toolchain. The whole tool fits in your head.
Why agents work cleanly with this shape
I work with a fleet of AI agents as coworkers. They edit the same site I do, but they don't use Freedom CMS's editor UI. They edit the HTML files directly through their filesystem tools.
The thing this enables is that we never disagree about state. Most CMSs have a database that's the source of truth and an HTML cache that's the deployment artifact. If I edit through the CMS UI and an agent edits the rendered HTML directly, those two changes can fight, and the resolution rules are tangled. There's also a discoverability problem: the agent has to know the CMS's API, its auth model, its content schema. None of that is the agent's job.
Freedom CMS doesn't have that problem. The HTML file is the only state. When I edit through the UI, I'm editing the file. When an agent edits, it's editing the same file. Both writes go to git. Both deploy via the same Cloudflare Pages hook. There is no sync layer to coordinate, and the agent doesn't need to learn anything beyond "edit the file, then commit."
This isn't a feature I planned. It's a property that fell out of "the file is the truth." Most CMSs put a layer between the editor and the deliverable because they wanted the editor to be richer; the side effect was that the layer became a thing both human collaborators and AI agents have to learn. Freedom CMS is small enough that there's no learning curve for either.
If you're building tooling around AI agents and you have a CMS in your stack, I'd genuinely consider whether the CMS is helping or whether it's putting a layer between your agents and the artifact. Often the answer is: just let the agents edit the file.
The trade-offs
This shape works for me. It does not work for everyone. Worth being honest about what I gave up.
There is no draft state. Once you save, it's committed and (after debounce) deployed. If you want to write something private and not publish it yet, you have to work in a branch.
There is no preview environment. The thing you're editing is the thing that's about to ship. For high-stakes pages this is genuinely scary; my workaround is to test the change locally first.
There is no schema. If a content editor wants the page to "have a hero with these three fields," there's no model enforcing it. The HTML can drift into shapes the design didn't intend.
There is no multi-user editing. Two people editing the same file at once is a git conflict, not a CMS coordination problem.
There is no localization, no content variation by audience, no A/B testing. The file IS the file.
For a personal site or a small product site with one editor, those trade-offs are fine. For a CMS replacement at a larger team, they aren't, and you should keep using the CMS you have.
But: for the use case where one person edits a site that ships HTML, deleting the layers between the editor and the HTML file made the editing experience genuinely faster than any CMS I've used in fifteen years. The HTML file IS the CMS. Everything else was scaffolding.