Building My Own Static Site Generator (Because Apparently I Hate Free Time)
I run four small personal sites. For years they lived on whatever publishing system seemed sensible at the time, which meant every one of them ended up on something different. One was hand-rolled HTML I edited directly. Another had picked up a full blog engine along the way. None of them agreed with each other, and every single one had its own list of quirks I had to remember before I could safely touch it.
Every time I wanted to make a small change, like updating a page template, I had to remember which tool that particular site used, dig up its documentation again, and hope the version I had installed still matched what the docs described. That’s not a workflow, that’s an obstacle course.
So I did the thing every developer eventually does when a tool annoys them enough: I decided to write my own.
Why Not Just Use an Existing One
There are a dozen perfectly good static site generators out there already, and I want to be honest that reaching for one of those would have been the sensible choice. But sensible wasn’t really the point. I wanted something with zero dependencies, so a Python version bump six years from now wouldn’t quietly break my blog. I wanted to actually understand every step between a markdown file and a finished HTML page instead of trusting a plugin ecosystem to get it right. And selfishly, I just wanted to know how this stuff works under the hood rather than treating it as a black box.
The result is ssg.py, a single Python file with no third party packages required. It reads a folder of markdown posts and pages, runs them through a small templating layer, and writes out a finished static site. All four of my sites now run on the exact same script, just with different content and templates pointed at it.
The Front Matter Parser
The first real design decision was how to handle front matter, the little block of metadata at the top of each post:
---
title: "A Post About Something"
date: "2026-09-15 12:00:00 +0000"
tags: [linux, python]
---
I didn’t want to pull in a YAML library just to parse a handful of key-value pairs, so I wrote a tiny parser that handles the subset of YAML I actually use: strings, dates, and simple lists. It’s not a general purpose YAML parser and it was never meant to be one. It just needs to correctly read the front matter I actually write, which turned out to be a much smaller problem than “support YAML.”
Templates Without a Templating Engine
The templating side works similarly. Rather than reaching for Jinja2, I built a small template layer that supports the handful of constructs I actually need: variable interpolation, loops over post lists, conditionals for things like pagination, and template inheritance so a post page and a plain page can share the same base layout. It’s nowhere near as capable as a real templating engine, and that’s fine, because it was never trying to be one. It only has to do what my own templates ask of it.
Pagination Turned Out to Be the Hard Part
Front matter parsing and templating were both fairly quick to get right. Pagination was the part that actually made me stop and think. Splitting posts into pages is easy. The fiddly bit is generating the right URLs for each page, keeping “previous” and “next” links correct at the edges, and making sure tag and category pages paginate the same way the main post index does, without duplicating that logic four separate times.
I ended up with one paginator class that every listing page runs through, whether it’s the homepage, a tag page, or a category page. Once that was in place, adding a new kind of listing page just meant handing it a list of posts and letting the shared paginator take care of the rest.
Was It Worth It
Reinventing the static site generator wheel is, on paper, a slightly ridiculous way to spend a few weekends. There’s a good chance the end result does less than whatever established tool I could have installed with one pip command. But I now understand exactly how my own sites build, I have exactly zero dependency updates to worry about, and moving all four sites onto one shared tool means I only ever have to remember one set of conventions.
I’ll take a slightly smaller feature set in exchange for never again forgetting which blog engine a particular site is running.