Technology8 min read

A new, bespoke static site generator to replace Jekyll

By · Published by Everything Blog

In short

The author, nullprogram, replaced Jekyll with ssg to improve performance, reduce deployment hassles, and avoid the slow Ruby deployment situation. The generator supports the author's blog, and the source code is included with the site, allowing for modifications without a template system. The author notes that ssg is faster than Jekyll and can be compiled into a single file, making it easy to run on other systems.

Key points

  • The author, nullprogram, replaced Jekyll with ssg to improve performance, reduce deployme…: The author, nullprogram, replaced Jekyll with ssg to improve performance, reduce deployment hassles, and avoid the slow Ruby deployment situation.
  • The generator supports the author's blog, and the source code is included with the site,…: The generator supports the author's blog, and the source code is included with the site, allowing for modifications without a template system.
  • The author notes that ssg is faster than Jekyll and can be compiled into a single file, m…: The author notes that ssg is faster than Jekyll and can be compiled into a single file, making it easy to run on other systems.

nullprogram.com/blog/2026/10/04/

My blog began as a blosxom (Perl) site running on a VPS. In 2011 I

moved to the new GitHub Pages, with the site generated statically

by Jekyll (Ruby) on a GitHub server. It was a no-brainer: easier,

faster, and cheaper, better in every way. After 15 years of Jekyll, this

week I replaced it with a new, custom-built static site generator,

dubbed ssg, in “C with templates” C++20. The ~8KLoC source closely

follows my personal coding style including templated arenas and

slices, zero dependencies, and a libc-free core. It’s wicked fast,

and a complete, cold generation of my blog takes 150ms on my MacBook. That

is, it’s done before Ruby would even reach Jekyll’s entry point. It’s a

been a great, real-world demonstration of the effectiveness of my coding

philosophy.

Look around and you’ll find almost nothing visually changed. Outside of

syntax highlighting, the HTML is semantically identical. I did not try to

match Rouge’s syntax highlighting, just its CSS selectors so that I could

use my style sheet unmodified. With that in my own hands, I now get syntax

highlighting that better suits my needs, some Rouge bugs gone and support

for new languages, particularly assembly (WAT, AT&T, Aarch64)

and QBasic.

Because ssg only supports my site, and its source lives along side it, I

don’t need a template system, i.e. Liquid. I can modify the C++ source as

needed. So the upgrade was three steps: (1) fix 19 years of accumulated

site bugs that Jekyll quietly tolerated, (2) add the new generator, (3)

delete Liquid templates and pre-generated tag pages (now dynamically

generated, with preservation of original feed UUIDs). Jekyll and ssg both

work simultaneously at step #2, allowing side-by-side comparisons from the

same site source. This is where most of the time was spent. Dropping

templates means performance comparisons with Jekyll are unfair, as Jekyll

is solving a different problem. (But I think it’s fair to speculate that

ssg with Liquid templates would still smoke Jekyll!)

Historically, Jekyll was a dreadfully slow site generator until the 4.0.0

release in August 2019. While performance is mostly resolved, I still

have ingrained habits to work around it. The final Jekyll version of my

site took ~2 seconds on the same MacBook. Not too bad, and it has a fast

--incremental

mode, though it’s always been a little buggy, not quite

matching a full generation.

No, the pain point for Jekyll is not performance but deployment.

Especially when trying to match the GitHub Pages configuration. The Ruby

deployment situation remains just awful. I never once generated my blog on

Windows, in part to avoid jumping through hoops to set up Jekyll. Last

year I switched to macOS (from Linux) as my primary, personal development

environment. This meant setting up Jekyll on macOS, and the situation is

farcical. The Ruby community ought to feel embarrassed about this.

The source

lines delay shell startup by 60ms, and I’m unwilling to bear

that cost in nearly every shell just to occasionally run Jekyll, plus

environment litter that may interfere with other tools. So I needed to

remember to enter an isolated Jekyll environment, and to tell AIs to use

if they needed Jekyll.

With ssg you just need a C++ compiler. As native application, you don’t

need any development tools once it’s built of course, and the compiled

program is a single file that could be copied to another system and run

as-is. It’s a unity build — I did say it closely follows my personal style

— so you don’t even need a build system. On w64devkit that looks like:

$ cc -nostartfiles -o _ssg/ssg.exe _ssg/main_windows.cpp

cc

(or gcc

) works because the compiler driver knows to invoke the C++

front-end for this input. It doesn’t use the C++ standard library, so the

linker doesn’t need -lstdc++

. -O0

builds like this run at about half

speed compared to optimized builds, and -O1

is sufficient to get all the

compiler optimization benefits. Normally debug builds would be around 10x

slower, but fast debug builds is par for the course for my style. While

you don’t need it, there is a CMake build, but that’s really just for

driving the test suite.

Both build and tool work on Windows XP, too, so for the first time ever I

could fully compose a post on my old XP laptop if I wished.

On the MacBook this -O0

build takes ~150ms — quite good for 8,372 lines

of code. A cold site generation takes this build ~260ms. That’s so fast I

could very well re-compile the generator each time I regenerate the site.

Indeed that’s exactly how it works in the GitHub Actions pipeline. Overall

it’s faster to use a debug build than a release build. The cache is too

slow and unreliable for release builds to make sense in Actions.

$ cc -std=c++20 -o _ssg/ssg _ssg/main_posix.cpp # macOS

For the first decade of GitHub Pages, Jekyll was the only option. It ran

opaquely in the somewhere at GitHub with a pass/fail result, requiring a

local matching configuration for debugging. When they introduced GitHub

Actions in 2018, Jekyll was a pre-configured, transparent pipeline, and it

became possible to run whatever generator you wanted in its place. I’m

finally taking advantage of that, and I still get automatic page builds on

push like I had with Jekyll. It’s now slightly faster — Actions overhead

dominates either way — despite compiling an entire C++ program each run.

I do not plan to divorce my generator from my blog, so ssg will not become

a general-purpose static site generator. Its whole purpose is to serve

this one particular need. You’re free to fork it and use it as a basis for

your own needs, of course. This sort of bespoke, written-to-order software

is likely the future of software. Why bother with one-site-fits-all when

an exactly-sized solution has the same cost?

A first for generative AI

I’ve been thinking about this project for years. Had I written it in the

2023–2025 time frame, I estimate ~2–4 weeks, using u-config (3KLoC,

lower complexity) as a measuring stick. Here in 2026, it took about

~20 minutes to write my detailed prompt, then ~2 hours for Opus 5.5 in

Claude Desktop to produce ssg in essentially its current form, bug-free as

far as I can tell, including an untested Win32 platform layer (written

on the MacBook, no Wine). I’ve spent more time on this article than I did

on the new generator.

I spent a couple more hours looking it over, shocked at Opus 5.5 perfectly

matching my personal style. It really does look like I wrote ssg. Reading

it is uncanny, like seeing someone reproduce my own handwriting such that

I couldn’t distinguish it. During this review I realized we ought to use

debug builds in the pipeline, so we had one follow-up on the hot spot in

debug builds. Then I noticed the Win32 platform layer generated an

empty site on Windows XP, another small followup. (XP wasn’t in

my original prompt, so I don’t count this as a bug.) That was it.

Two years ago I said that AI-generated code was too poor to be

practical. That changed by the end of 2025. In earlier 2026 projects I

tried to get AI to write C following my style, using my writing on the

subject as a guide, but they’d just recreate the no-good standard

library from scratch then flub arena allocation. Ask those models

to write conventional C and you get the usual, error-prone C you’ll find

anywhere. So I settled on conventional C++ as Good Enough. I could

complete projects ~20x faster, but with results not quite as good as I’d

have done myself. A reasonable trade-off.

Opus 5.5 released on September 22nd. It is the first model I’ve used that

actually understands my coding philosophy and writes C at least as well

as me. This was the ideal project for this test, as my core writing on

this subject was naturally in context, but it generalizes when referencing

my writing and prior work. As of two weeks ago I can now have my cake and

eat it, too. No more trade-offs.

If you’d like to get similar C-with-templates results on your own project,

cite these four articles in your prompt:

As well as perhaps my relevant, similar projects. All the frontier models

know about me personally and are familiar with my work, so invoking my

name may be enough. If the model is as good as Opus 5.5, you might get an

accurate impression of how I would have done your project.

Original source: nullprogram.com

Technology