The Git Based Headless CMS Renaissance: Why I Moved My Blog to Sveltia CMS
I recently watched Kohei Yoshino’s talk on Sveltia CMS, and it put words to something I’d been cobbling together for my own blog: a free, easy to maintain publishing stack where Git is the only source of truth.
This is the version of that story after I actually finished wiring it up, including the three gotchas that nearly stopped it working.
The stack
It’s deliberately small:
- Site: Astro, static output, handwritten CSS (a dark theme with a terminal feel)
- Source control: a GitHub repository
- Host: Cloudflare Pages, connected directly to that repo
- CMS: Sveltia CMS, loaded as a single JavaScript bundle from a CDN at
/admin
Content lives as Markdown files with YAML front matter. Cloudflare runs astro build on every push to main and serves the result from its edge network. Sveltia sits on top as a friendly editor, so publishing a post no longer means writing Markdown by hand and pushing a commit myself.
Sveltia itself is agnostic to framework and host, a modern, actively maintained successor to Netlify/Decap CMS, focused on the editing experience. Cloudflare is just my pick for static hosting, not something the project pushes you toward.
Gotcha one: the CMS isn’t a hosted service anymore
The first thing I hit: the old cms.sveltia.app URL no longer resolves. Sveltia CMS is now a JavaScript application distributed as a single bundle over a CDN. You don’t point an iframe at a hosted dashboard; you drop a <script> tag into your own /admin page:
<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
Add a config.yml that points the CMS at your repo. Once I stopped looking for a hosted app and simply embedded the bundle, the editor appeared.
Gotcha two: “Sign in with GitHub” points at Netlify
This one took longer. Out of the box, Sveltia’s “Sign in with GitHub” button redirects to api.netlify.com. That’s leftover behaviour from its Decap CMS lineage. Off Netlify, that endpoint just 404s, and you’re left staring at a login that never completes.
There are two real ways past it:
- An OAuth proxy. Sveltia publishes a tiny Cloudflare Worker you can deploy yourself, plus a GitHub OAuth app. Wire its URL into
base_urlin your config, and the GitHub button works directly. Worth it for a team. - A personal access token. For a solo blog like mine, that’s overkill. Sveltia also offers “Sign in with Token”: you generate a GitHub token with
reposcope, paste it in, and it lives in your browser. No server, no OAuth app, no secrets to rotate beyond the token itself.
I went with the token. One author, one browser, done.
Gotcha three: direct upload isn’t git integration
This was the subtle one, and the part I’d gotten wrong at first. There are two ways to put a site on Cloudflare Pages: Direct Upload (you run wrangler pages deploy and push files yourself) and Git integration (Cloudflare watches your repo and builds on every push). From the outside they look identical, with the same pages.dev URL and the same custom domain, but they are not interchangeable. Git integration is a decision you make when you create a project, and you can’t switch it on for one that already exists.
I’d been deploying with Direct Upload. So even after Sveltia was committing happily to main, nothing was rebuilding. The CMS and the host were two halves of a loop that never closed.
The fix was to create a fresh project connected to Git, point it at the repo with a build command (npm run build, output dist), move the custom domain across, and retire the old one. Only then did the loop actually run: I edit in Sveltia, it commits to main, Cloudflare builds the Astro site, and why.web.id updates a minute or two later, with no command line anywhere in the process.
Why it’s worth it
The appeal isn’t only that it’s free, though it is. The sole recurring cost is the domain name, and even that’s optional if a *.pages.dev subdomain will do. It’s that nothing in the stack owns my content. Posts are plain Markdown in a repo I control. Every edit is a commit, so I get history and rollback for free. There’s no admin panel exposed to the internet for bots to hammer, no database to back up, no plugin ecosystem to keep patched.
What Yoshino’s talk frames as a “renaissance” feels accurate. For a long time a “real” CMS meant an application server backed by a database. These CMSs quietly inverted that: content becomes text in version control, the site becomes a build artifact, and hosting collapses to serving static files from a CDN edge.
If you maintain a personal site, blog, or docs project and haven’t tried this approach, it’s worth an afternoon, plus a little patience for the three gotchas above. Once it’s wired up, it mostly runs itself.