I don’t need a full CMS. I need a publishing workflow that stays out of my way.

The Problem with Traditional CMS

WordPress, Ghost, and their cousins all share the same assumption: your content lives in a database, and the CMS controls access to it. That means servers, logins, attack surfaces, and maintenance.

For a personal site, that’s overkill. I want to write, review, schedule, and publish. I don’t need user management, plugins, or a PHP runtime.

The Folder-Based Approach

My content lives in folders:

content/
  drafts/      → Work in progress
  review/      → Ready for editing
  scheduled/   → Waiting for publish date
  published/   → Live on the site
  private/     → Never deployed

Moving a file from one folder to another changes its status. That’s it. No database, no admin panel, no state to manage. Just files and git.

The Stack

Everything runs on tools that already exist:

  • Hugo generates the static site
  • GitHub Pages hosts it for free
  • GitHub Actions builds and deploys on every push to main
  • Decap CMS provides a local editor (never deployed to production)
  • Git handles versioning and collaboration

No servers to maintain. No databases to back up. No security patches to apply.

Planned Automations

I’m building out a set of automations to handle the repetitive parts:

Scheduled Publishing

A GitHub Action that runs daily, checks content/scheduled/ for posts where the publish date has arrived, moves them to content/published/, and triggers a rebuild. No more manual publishing on specific dates.

Auto-Commit from Local Editor

When I edit content in Decap CMS locally, a file watcher can detect changes and commit them automatically. Removes the friction of manual git commands after every edit.

Preview Deploys

A separate deployment of the develop branch that includes all content, not just published posts. Useful for reviewing drafts and scheduled content before it goes live.

Workflow Scripts

Simple npm scripts to move content between folders:

  • npm run promote <file> moves a file to the next stage
  • npm run status shows all content and its current stage
  • npm run publish <file> moves directly to published

Content Quality Checks

Spell checking and link validation on pull requests. Catches broken links and typos before they go live.

Social Sharing Images

Auto-generated Open Graph images for each post. Better previews when sharing on social media without manually creating images.

External Image Hosting

Integration with Cloudinary or similar to keep large images out of the git repo. Faster clones, automatic optimization, CDN delivery.

Why Assemble Instead of Install

One of the first open source projects I worked on was Plone, a Python-based CMS that predates Django. I never got my head around all of its complexities. That was litterally decades ago, but the lesson stuck: if you can’t understand the system, you can’t control it.

I could use an off-the-shelf static site CMS. Several exist. But assembling from parts gives me:

  1. Understanding: I know exactly what every piece does
  2. Control: I can modify any component without fighting a framework
  3. Reusability: Every automation I build here works in other projects
  4. Simplicity: No features I don’t need, no complexity I didn’t choose

This is the same approach I use for DollhouseMCP and Merview. Build tools from composable parts. Extract libraries when patterns emerge. Share improvements across projects.

Fixes Along the Way

Building from parts means you encounter quirks. Here’s one we hit on day one.

Disabling GoAT Diagram Conversion

Hugo has a feature called GoAT that converts ASCII art in code blocks into SVG diagrams. It’s meant for rendering boxes and arrows as clean vector graphics.

The problem: Hugo detected the arrow characters in my folder workflow diagram and converted the entire code block into an SVG with each character as a separate element. The result was unreadable.

The fix: a one-line render hook.

Create layouts/_default/_markup/render-codeblock-goat.html:

<pre><code>{{ .Inner }}</code></pre>

This intercepts any block Hugo thinks is a GoAT diagram and outputs it as normal code instead. Problem solved in under a minute.

This is exactly why assembling from parts works. When something breaks, you can see what’s happening and fix it directly. No waiting for a plugin update or filing a support ticket.

Current Status

The basic workflow is functional:

  • Site builds and deploys automatically
  • Local editing works via Decap CMS
  • Folder structure enforces workflow stages
  • Environment configs separate development from production
  • Code blocks render properly (GoAT disabled)

The automations listed above are next. Each one removes friction and makes the system more useful.

Open Source

This site’s code will eventually be public once I clean up the initial setup. The goal is a template others can use: fork, customize, deploy. A lightweight CMS, nothing fancy.