Skip to main content
Back to Research

Git-First CMS Architecture Explained

Traditional headless CMS databases create operational coupling that undermines static site generation. Moving content to Git eliminates the coupling…

Abstract

An architectural decision record expanded into a full field report: the motivations, tradeoffs, implementation details, and operational consequences of migrating a Next.js portfolio site from Supabase-backed content to Git-committed MDX files.

June 10, 2026
8 min read

We deprecated Supabase. Not because Supabase is bad — it is an excellent product. We deprecated it because we had been using it for a purpose it was not the right tool for, and the cost of that mismatch had been accumulating quietly for months before we named it.

This is the architecture decision record for ADR-008, expanded with the context and operational consequences that never fit in a formal ADR.

The Problem We Were Solving

The original architecture used Supabase as the backend for journal content. Articles were stored in a journal_posts table. The admin panel could create, edit, and publish articles. The public site fetched published articles from Supabase at build time via server-side data fetching.

On paper, this makes sense. A CMS with a database backend is a well-understood pattern. The problem was the mismatch with our constraints:

  • The site uses output: 'export' — fully static HTML generation, no server runtime
  • The build must produce the same output on every CI run (reproducibility)
  • Article versioning should be visible in Git history, not buried in database change logs
  • Articles are technical documents that benefit from code review, not just editorial review

With Supabase as the content source, every build was coupled to the availability and state of an external service. If Supabase was unreachable, the build failed. If someone published an article while CI was running, different builds produced different outputs. The database was a build-time dependency, not a runtime dependency — and that is a fundamentally different (and worse) operational model.

What "Git-First" Actually Means

Editor / IDE .mdx file Git + GitHub PR → merge CI / Build next build CF Pages published commit = version hermetic build CDN edge
Git-First publish pipeline — edit → commit → merge → build → deploy

Git-first means the repository is the canonical source of truth for all content. Every article is a committed MDX file. Every edit is a commit. Every review is a pull request. Publication is a merge to main followed by a Cloudflare Pages deploy.

The implications compound:

  • Build reproducibility — a specific git commit hash produces a deterministic build output, always
  • Versioning for freegit log src/content/articles/my-post.mdx shows the full history of every edit
  • Rollback for freegit revert undoes a publication in one command, and the next deploy reflects it
  • No external service dependency at build time — the build is hermetic
  • Code review for content — the same PR process that reviews code changes can review article drafts

What it does not mean is that the admin interface disappears. The admin interface still exists. In the transition, it became read-only — it can display content metrics and analytics, but it cannot write new content. Writing happens through the file system and Git. Phase 3 of the CMS migration will restore write capability through a Content API on the VPS that commits directly to the repository.

The Migration

Moving from Supabase to Git content required:

1. MDX Frontmatter Schema

Every article needed a consistent frontmatter schema that captured what the database schema had captured. The mapping was mostly straightforward:

---
slug: my-article-slug
title: Article Title
excerpt: One-line description for cards and RSS
abstract: Longer abstract for article detail pages
date: "2026-06-10T00:00:00.000Z"
category: research | essays | opinion | news
depth: quick-read | deep-read
tags:
  - tag-one
  - tag-two
readTime: 10
coverImage: /og/en/git-first-cms-architecture.png
---

The only field that required design work was status. In the database, articles had a status field that was set to published, draft, or archived. In Git-first, the status is implicit: committed to main = published, branch = draft, deleted = archived. We kept an optional status field for cases where we want a committed file that is not publicly visible — useful for content under review that has already been merged to main.

2. Content Loaders

The build-time content loading moved from Supabase client calls to file system reads with gray-matter parsing. The loader produces the same typed output that the Supabase version produced, so component code required zero changes:

// src/lib/content/loaders.ts
import { readFileSync, readdirSync } from 'fs'
import { join } from 'path'
import matter from 'gray-matter'

const ARTICLES_DIR = join(process.cwd(), 'src', 'content', 'articles')

export function loadArticles(): Article[] {
  const files = readdirSync(ARTICLES_DIR).filter(f => f.endsWith('.mdx'))
  return files
    .map(filename => {
      const raw     = readFileSync(join(ARTICLES_DIR, filename), 'utf-8')
      const { data } = matter(raw)
      return parseArticleFrontmatter(data, filename)
    })
    .filter(a => a.status !== 'draft')
    .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
}

3. Scripts That Used Supabase

The RSS generator and OG image generator both fetched from Supabase. Both were rewritten to read from MDX. The Supabase fetch was replaced with the same gray-matter file reader, and the output was identical. These scripts are now hermetic — they run without network access, which is correct for build-time scripts.

The Tradeoffs

Git-first is not strictly better than database-backed in all dimensions. The honest tradeoffs:

Better: Build reproducibility, versioning, rollback, no external service dependency, code review for content, offline editing

Worse: Non-technical editors cannot self-serve (they need to understand Markdown and Git, or use a Git-backed CMS UI like Netlify CMS or Tina), real-time content preview requires running the build, structured queries across content require file system traversal rather than SQL

For a developer portfolio where the author is the only editor, the tradeoffs strongly favor Git-first. For a multi-editor publication where marketing team members need to publish autonomously, a database-backed CMS with a proper editorial interface is the right tool.

Phase 3 — Restoring Write Capability

The current state is read-only admin. Phase 3 will add a Content API on the Hostinger VPS that:

  1. Accepts authenticated write requests (article create/update) from the admin UI
  2. Commits the MDX file to the repository via the GitHub API
  3. Triggers a Cloudflare Pages deploy via webhook

This restores the full authoring workflow — write from the admin panel, content goes to Git, site redeploys automatically — while keeping Git as the canonical source of truth. The VPS is a write proxy, not the content store. If the VPS goes down, the site continues to serve content. The repository is always the authority.

The operational model is: GitHub is the database. Cloudflare Pages is the delivery layer. The VPS is the write API. No single point of failure can take down both the site and the ability to publish corrections.

Lessons

The lesson is not "databases are bad for content." The lesson is that the canonical source of truth for a static site should be compatible with the static site's operational model. Our static site has no server runtime and needs hermetic builds. Those requirements make Git a better fit than a database for this specific context.

The broader pattern: before choosing infrastructure, state your constraints explicitly. "Static site that must build without network access" is a constraint that directly determines what content source is appropriate. Most teams skip the constraint definition and jump to tool selection, which is why they end up rebuilding the architecture later.

Continue Reading
JCJOOTACEE / OPS

Operational laboratory for AI systems, automation infrastructures, and modular digital ecosystems.

Systems

  • AURA Orchestration
  • MCP Ecosystem
  • Graph Memory
  • AI Agents
  • Docker Infrastructure
  • Industrial Intelligence

System Status

PlatformOperational
APIHealthy
3D EngineActive
MCP Nodes8 Online

Try the Konami code...

Stay in the loop

Occasional updates on AI systems, autonomous infrastructure, and new releases.

© 2026 JootaCee. All systems operational.

RSSChangelogNext.js 16 + React 19 + R3F + GSAP