{"article":{"slug":"a-new-bespoke-static-site-generator-to-replace-jekyll","title":"A new, bespoke static site generator to replace Jekyll","subtitle":null,"summary":"Chris Wellons replaces 15 years of Jekyll on nullprogram.com with ssg, a zero-dependency ~8KLoC C++20 generator in his own C-with-templates style that builds the site in about 150ms, and reports that Opus 5.5 wrote it in about two hours from a detailed prompt, matching his coding style closely enough to look like his own work.","content_type":"blog_post","language":"en","canonical_url":"https://nullprogram.com/blog/2026/10/04/","author":{"name":"Chris Wellons","url":"https://nullprogram.com/","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"null program","url":"https://nullprogram.com/","listing_slug":null,"listing":null},"topics":[{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"},{"name":"AI","slug":"ai","url":"https://listedarticles.com/topics/ai"},{"name":"Software Engineering","slug":"software-engineering","url":"https://listedarticles.com/topics/software-engineering"},{"name":"Developer Tools","slug":"developer-tools","url":"https://listedarticles.com/topics/developer-tools"}],"about_listings":[{"slug":"anthropic","name":"Anthropic","listing_type":"company","url":"https://listedstartups.com/companies/anthropic"},{"slug":"claude","name":"Claude","listing_type":"product","url":"https://listedstartups.com/products/claude"}],"cover_image_url":null,"license":"CC0-1.0","word_count":1473,"reading_minutes":6,"published_at":"2026-10-04T00:00:00.000Z","added_at":"2026-10-05T11:11:13.262Z","updated_at":"2026-10-05T11:11:13.262Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/a-new-bespoke-static-site-generator-to-replace-jekyll","markdown_url":"https://listedarticles.com/articles/a-new-bespoke-static-site-generator-to-replace-jekyll.md","example":false,"citation":"Chris Wellons, null program. \"A new, bespoke static site generator to replace Jekyll.\" 4 Oct 2026. https://nullprogram.com/blog/2026/10/04/ (CC0-1.0)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://nullprogram.com/blog/2026/10/04/"},"body_markdown":"# A new, bespoke static site generator to replace Jekyll\n\n  My blog began as a [blosxom](https://www.blosxom.com/) (Perl) site running on a VPS. In 2011 I\n[moved to the new GitHub Pages](https://nullprogram.com/blog/2011/08/05/), with the site generated statically\nby [Jekyll](https://jekyllrb.com/) (Ruby) on a GitHub server. It was a no-brainer: easier,\nfaster, and cheaper, better in every way. After 15 years of Jekyll, this\nweek I replaced it with [a new, custom-built static site generator](https://github.com/skeeto/skeeto.github.com/tree/master/_ssg),\ndubbed *ssg*, in “C with templates” C++20. The ~8KLoC source [closely\nfollows my personal coding style](https://nullprogram.com/blog/2025/01/19/) including [templated arenas and\nslices](https://nullprogram.com/blog/2024/04/14/), zero dependencies, and a libc-free core. It’s wicked fast,\nand a complete, cold generation of my blog takes 150ms on my MacBook. That\nis, *it’s done before Ruby would even reach Jekyll’s entry point*. It’s a\nbeen a great, real-world demonstration of the effectiveness of my coding\nphilosophy.\n\nLook around and you’ll find almost nothing visually changed. Outside of\nsyntax highlighting, the HTML is semantically identical. I did not try to\nmatch Rouge’s syntax highlighting, just its CSS selectors so that I could\nuse my style sheet unmodified. With that in my own hands, I now get syntax\nhighlighting that better suits my needs, some Rouge bugs gone and support\nfor new languages, particularly assembly ([WAT](https://nullprogram.com/blog/2025/04/04/), [AT&T](https://nullprogram.com/blog/2024/02/05/), [Aarch64](https://nullprogram.com/blog/2022/02/18/))\nand [QBasic](https://nullprogram.com/blog/2020/11/17/).\n\nBecause 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!)\n\nHistorically, Jekyll was a dreadfully slow site generator until [the 4.0.0\nrelease in August 2019](https://jekyllrb.com/news/2019/08/20/jekyll-4-0-0-released/). While performance is mostly resolved, I still\nhave ingrained habits to work around it. The final Jekyll version of my\nsite took ~2 seconds on the same MacBook. Not too bad, and it has a fast\n`--incremental` mode, though it’s always been a little buggy, not quite\nmatching a full generation.\n\n**No, the pain point for Jekyll is not performance but *deployment***.\nEspecially when trying to match the GitHub Pages configuration. The Ruby\ndeployment situation remains just awful. I never once generated my blog on\nWindows, in part to avoid jumping through hoops to set up Jekyll. Last\nyear I switched to macOS (from Linux) as my primary, personal development\nenvironment. This meant setting up Jekyll on macOS, and [the situation is\nfarcical](https://jekyllrb.com/docs/installation/macos/). The Ruby community ought to feel embarrassed about this.\nThe `source` lines delay shell startup by 60ms, and I’m unwilling to bear\nthat cost in nearly every shell just to occasionally run Jekyll, plus\nenvironment litter that may interfere with other tools. So I needed to\nremember to enter an isolated Jekyll environment, and to tell AIs to use\nif they needed Jekyll.\n\nWith ssg you just need a C++ compiler. As native application, you don’t\nneed any development tools once it’s built of course, and the compiled\nprogram is a single file that could be copied to another system and run\nas-is. It’s a unity build — I did say it closely follows my personal style\n— so you don’t even need a build system. On [w64devkit](https://github.com/skeeto/w64devkit) that looks like:\n\n```\n$ cc -nostartfiles -o _ssg/ssg.exe _ssg/main_windows.cpp\n```\n`cc` (or `gcc`) works because the compiler driver knows to invoke the C++\nfront-end for this input. It doesn’t use the C++ standard library, so the\nlinker doesn’t need `-lstdc++`. `-O0` builds like this run at about half\nspeed compared to optimized builds, and `-O1` is sufficient to get all the\ncompiler optimization benefits. Normally debug builds would be around 10x\nslower, but fast debug builds is par for the course for my style. While\nyou don’t need it, there *is* a CMake build, but that’s really just for\ndriving the test suite.\n\nBoth build and tool work on Windows XP, too, so for the first time ever I\ncould [fully compose a post on my old XP laptop](https://nullprogram.com/blog/2025/03/02/) if I wished.\n\nOn the MacBook this `-O0` build takes ~150ms — quite good for 8,372 lines\nof code. A cold site generation takes this build ~260ms. That’s so fast I\ncould very well re-compile the generator each time I regenerate the site.\nIndeed that’s exactly how it works in the GitHub Actions pipeline. Overall\nit’s faster to use a debug build than a release build. The cache is too\nslow and unreliable for release builds to make sense in Actions.\n\n```\n$ cc -std=c++20 -o _ssg/ssg _ssg/main_posix.cpp  # macOS\n```\nFor 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.\n\nI 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?\n\n### A first for generative AI\n\nI’ve been thinking about this project for years. Had I written it in the\n2023–2025 time frame, I estimate ~2–4 weeks, using [u-config](https://nullprogram.com/blog/2023/01/18/) (3KLoC,\nlower complexity) as a measuring stick. Here in [2026](https://nullprogram.com/blog/2026/03/29/), it took about\n~20 minutes to write my detailed prompt, then ~2 hours for Opus 5.5 in\nClaude Desktop to produce ssg in essentially its current form, bug-free as\nfar as I can tell, including an *untested* Win32 platform layer (written\non the MacBook, no Wine). I’ve spent more time on this article than I did\non the new generator.\n\nI spent a couple more hours looking it over, shocked at Opus 5.5 perfectly\nmatching my personal style. It really does look like I wrote ssg. Reading\nit is uncanny, like seeing someone reproduce my own handwriting such that\nI couldn’t distinguish it. During this review I realized we ought to use\ndebug builds in the pipeline, so we had [one follow-up on the hot spot in\ndebug builds](https://github.com/skeeto/skeeto.github.com/commit/2c9aa4e9). Then I noticed the Win32 platform layer generated an\nempty site on Windows XP, [another small followup](https://github.com/skeeto/skeeto.github.com/commit/35228acf). (XP wasn’t in\nmy original prompt, so I don’t count this as a bug.) That was it.\n\nTwo years ago [I said](https://nullprogram.com/blog/2024/11/10/) that AI-generated code was too poor to be\npractical. That changed by the end of 2025. In earlier 2026 projects I\ntried to get AI to write C following my style, using my writing on the\nsubject as a guide, but they’d just recreate the [no-good standard\nlibrary](https://nullprogram.com/blog/2023/02/11/) from scratch then flub arena allocation. Ask those models\nto write conventional C and you get the usual, error-prone C you’ll find\nanywhere. So I settled on conventional C++ as Good Enough. I could\ncomplete projects ~20x faster, but with results not quite as good as I’d\nhave done myself. A reasonable trade-off.\n\nOpus 5.5 released on September 22nd. It is the first model I’ve used that\nactually understands my coding philosophy and **writes C at least as well\nas me**. This was the ideal project for this test, as my core writing on\nthis subject was naturally in context, but it generalizes when referencing\nmy writing and prior work. As of two weeks ago I can now have my cake and\neat it, too. No more trade-offs.\n\nIf you’d like to get similar C-with-templates results on your own project, cite these four articles in your prompt:\n\n- [https://nullprogram.com/blog/2025/01/19/](https://nullprogram.com/blog/2025/01/19/)\n- [https://nullprogram.com/blog/2024/04/14/](https://nullprogram.com/blog/2024/04/14/)\n- [https://nullprogram.com/blog/2025/09/30/](https://nullprogram.com/blog/2025/09/30/)\n- [https://nullprogram.com/blog/2025/10/16/](https://nullprogram.com/blog/2025/10/16/)\n\nAs 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.\n","body_html":"<h1 id=\"a-new-bespoke-static-site-generator-to-replace-jekyll\">A new, bespoke static site generator to replace Jekyll</h1>\n<p>  My blog began as a <a href=\"https://www.blosxom.com/\" rel=\"nofollow ugc noopener\">blosxom</a> (Perl) site running on a VPS. In 2011 I\n<a href=\"https://nullprogram.com/blog/2011/08/05/\" rel=\"nofollow ugc noopener\">moved to the new GitHub Pages</a>, with the site generated statically\nby <a href=\"https://jekyllrb.com/\" rel=\"nofollow ugc noopener\">Jekyll</a> (Ruby) on a GitHub server. It was a no-brainer: easier,\nfaster, and cheaper, better in every way. After 15 years of Jekyll, this\nweek I replaced it with <a href=\"https://github.com/skeeto/skeeto.github.com/tree/master/_ssg\" rel=\"nofollow ugc noopener\">a new, custom-built static site generator</a>,\ndubbed <em>ssg</em>, in “C with templates” C++20. The ~8KLoC source <a href=\"https://nullprogram.com/blog/2025/01/19/\" rel=\"nofollow ugc noopener\">closely\nfollows my personal coding style</a> including <a href=\"https://nullprogram.com/blog/2024/04/14/\" rel=\"nofollow ugc noopener\">templated arenas and\nslices</a>, zero dependencies, and a libc-free core. It’s wicked fast,\nand a complete, cold generation of my blog takes 150ms on my MacBook. That\nis, <em>it’s done before Ruby would even reach Jekyll’s entry point</em>. It’s a\nbeen a great, real-world demonstration of the effectiveness of my coding\nphilosophy.</p>\n<p>Look around and you’ll find almost nothing visually changed. Outside of\nsyntax highlighting, the HTML is semantically identical. I did not try to\nmatch Rouge’s syntax highlighting, just its CSS selectors so that I could\nuse my style sheet unmodified. With that in my own hands, I now get syntax\nhighlighting that better suits my needs, some Rouge bugs gone and support\nfor new languages, particularly assembly (<a href=\"https://nullprogram.com/blog/2025/04/04/\" rel=\"nofollow ugc noopener\">WAT</a>, <a href=\"https://nullprogram.com/blog/2024/02/05/\" rel=\"nofollow ugc noopener\">AT&amp;T</a>, <a href=\"https://nullprogram.com/blog/2022/02/18/\" rel=\"nofollow ugc noopener\">Aarch64</a>)\nand <a href=\"https://nullprogram.com/blog/2020/11/17/\" rel=\"nofollow ugc noopener\">QBasic</a>.</p>\n<p>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!)</p>\n<p>Historically, Jekyll was a dreadfully slow site generator until <a href=\"https://jekyllrb.com/news/2019/08/20/jekyll-4-0-0-released/\" rel=\"nofollow ugc noopener\">the 4.0.0\nrelease in August 2019</a>. While performance is mostly resolved, I still\nhave ingrained habits to work around it. The final Jekyll version of my\nsite took ~2 seconds on the same MacBook. Not too bad, and it has a fast\n<code>--incremental</code> mode, though it’s always been a little buggy, not quite\nmatching a full generation.</p>\n<p><strong>No, the pain point for Jekyll is not performance but <em>deployment</strong></em>.\nEspecially when trying to match the GitHub Pages configuration. The Ruby\ndeployment situation remains just awful. I never once generated my blog on\nWindows, in part to avoid jumping through hoops to set up Jekyll. Last\nyear I switched to macOS (from Linux) as my primary, personal development\nenvironment. This meant setting up Jekyll on macOS, and <a href=\"https://jekyllrb.com/docs/installation/macos/\" rel=\"nofollow ugc noopener\">the situation is\nfarcical</a>. The Ruby community ought to feel embarrassed about this.\nThe <code>source</code> lines delay shell startup by 60ms, and I’m unwilling to bear\nthat cost in nearly every shell just to occasionally run Jekyll, plus\nenvironment litter that may interfere with other tools. So I needed to\nremember to enter an isolated Jekyll environment, and to tell AIs to use\nif they needed Jekyll.</p>\n<p>With ssg you just need a C++ compiler. As native application, you don’t\nneed any development tools once it’s built of course, and the compiled\nprogram is a single file that could be copied to another system and run\nas-is. It’s a unity build — I did say it closely follows my personal style\n— so you don’t even need a build system. On <a href=\"https://github.com/skeeto/w64devkit\" rel=\"nofollow ugc noopener\">w64devkit</a> that looks like:</p>\n<pre><code>$ cc -nostartfiles -o _ssg/ssg.exe _ssg/main_windows.cpp</code></pre>\n<p><code>cc</code> (or <code>gcc</code>) works because the compiler driver knows to invoke the C++\nfront-end for this input. It doesn’t use the C++ standard library, so the\nlinker doesn’t need <code>-lstdc++</code>. <code>-O0</code> builds like this run at about half\nspeed compared to optimized builds, and <code>-O1</code> is sufficient to get all the\ncompiler optimization benefits. Normally debug builds would be around 10x\nslower, but fast debug builds is par for the course for my style. While\nyou don’t need it, there <em>is</em> a CMake build, but that’s really just for\ndriving the test suite.</p>\n<p>Both build and tool work on Windows XP, too, so for the first time ever I\ncould <a href=\"https://nullprogram.com/blog/2025/03/02/\" rel=\"nofollow ugc noopener\">fully compose a post on my old XP laptop</a> if I wished.</p>\n<p>On the MacBook this <code>-O0</code> build takes ~150ms — quite good for 8,372 lines\nof code. A cold site generation takes this build ~260ms. That’s so fast I\ncould very well re-compile the generator each time I regenerate the site.\nIndeed that’s exactly how it works in the GitHub Actions pipeline. Overall\nit’s faster to use a debug build than a release build. The cache is too\nslow and unreliable for release builds to make sense in Actions.</p>\n<pre><code>$ cc -std=c++20 -o _ssg/ssg _ssg/main_posix.cpp  # macOS</code></pre>\n<p>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.</p>\n<p>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?</p>\n<h3 id=\"a-first-for-generative-ai\">A first for generative AI</h3>\n<p>I’ve been thinking about this project for years. Had I written it in the\n2023–2025 time frame, I estimate ~2–4 weeks, using <a href=\"https://nullprogram.com/blog/2023/01/18/\" rel=\"nofollow ugc noopener\">u-config</a> (3KLoC,\nlower complexity) as a measuring stick. Here in <a href=\"https://nullprogram.com/blog/2026/03/29/\" rel=\"nofollow ugc noopener\">2026</a>, it took about\n~20 minutes to write my detailed prompt, then ~2 hours for Opus 5.5 in\nClaude Desktop to produce ssg in essentially its current form, bug-free as\nfar as I can tell, including an <em>untested</em> Win32 platform layer (written\non the MacBook, no Wine). I’ve spent more time on this article than I did\non the new generator.</p>\n<p>I spent a couple more hours looking it over, shocked at Opus 5.5 perfectly\nmatching my personal style. It really does look like I wrote ssg. Reading\nit is uncanny, like seeing someone reproduce my own handwriting such that\nI couldn’t distinguish it. During this review I realized we ought to use\ndebug builds in the pipeline, so we had <a href=\"https://github.com/skeeto/skeeto.github.com/commit/2c9aa4e9\" rel=\"nofollow ugc noopener\">one follow-up on the hot spot in\ndebug builds</a>. Then I noticed the Win32 platform layer generated an\nempty site on Windows XP, <a href=\"https://github.com/skeeto/skeeto.github.com/commit/35228acf\" rel=\"nofollow ugc noopener\">another small followup</a>. (XP wasn’t in\nmy original prompt, so I don’t count this as a bug.) That was it.</p>\n<p>Two years ago <a href=\"https://nullprogram.com/blog/2024/11/10/\" rel=\"nofollow ugc noopener\">I said</a> that AI-generated code was too poor to be\npractical. That changed by the end of 2025. In earlier 2026 projects I\ntried to get AI to write C following my style, using my writing on the\nsubject as a guide, but they’d just recreate the <a href=\"https://nullprogram.com/blog/2023/02/11/\" rel=\"nofollow ugc noopener\">no-good standard\nlibrary</a> from scratch then flub arena allocation. Ask those models\nto write conventional C and you get the usual, error-prone C you’ll find\nanywhere. So I settled on conventional C++ as Good Enough. I could\ncomplete projects ~20x faster, but with results not quite as good as I’d\nhave done myself. A reasonable trade-off.</p>\n<p>Opus 5.5 released on September 22nd. It is the first model I’ve used that\nactually understands my coding philosophy and <strong>writes C at least as well\nas me</strong>. This was the ideal project for this test, as my core writing on\nthis subject was naturally in context, but it generalizes when referencing\nmy writing and prior work. As of two weeks ago I can now have my cake and\neat it, too. No more trade-offs.</p>\n<p>If you’d like to get similar C-with-templates results on your own project, cite these four articles in your prompt:</p>\n<ul><li><a href=\"https://nullprogram.com/blog/2025/01/19/\" rel=\"nofollow ugc noopener\"><a href=\"https://nullprogram.com/blog/2025/01/19/\" rel=\"nofollow ugc noopener\">https://nullprogram.com/blog/2025/01/19/</a></a></li><li><a href=\"https://nullprogram.com/blog/2024/04/14/\" rel=\"nofollow ugc noopener\"><a href=\"https://nullprogram.com/blog/2024/04/14/\" rel=\"nofollow ugc noopener\">https://nullprogram.com/blog/2024/04/14/</a></a></li><li><a href=\"https://nullprogram.com/blog/2025/09/30/\" rel=\"nofollow ugc noopener\"><a href=\"https://nullprogram.com/blog/2025/09/30/\" rel=\"nofollow ugc noopener\">https://nullprogram.com/blog/2025/09/30/</a></a></li><li><a href=\"https://nullprogram.com/blog/2025/10/16/\" rel=\"nofollow ugc noopener\"><a href=\"https://nullprogram.com/blog/2025/10/16/\" rel=\"nofollow ugc noopener\">https://nullprogram.com/blog/2025/10/16/</a></a></li></ul>\n<p>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.</p>","headings":[{"level":1,"text":"A new, bespoke static site generator to replace Jekyll","id":"a-new-bespoke-static-site-generator-to-replace-jekyll"},{"level":3,"text":"A first for generative AI","id":"a-first-for-generative-ai"}]}}