How Not to Make a Website

November 15, 2025

It’s 2025. How hard can it be to make a personal website?

I had the following requirements, in priority order:

Most of the criteria speak for themselves. For good typography, paradigm cases include gwern.net and Butterick’s Practical Typography. For extensibility, we have Emacs. And for how the web could have worked, we have the visions of Ted Nelson.

The obvious choices, upon speaking with the friendly neighborhood LLM, are Hugo and Jekyll. But I’m special damnit, I want to do things my way. So down we go into the rabbit hole of niche static website generators.

Hakyll (Haskell)

Inspired by Gwern, I started with Hakyll. Let’s look at the requirements:

Requirement: Status
well maintained: good enough
static website: yes
blog posts and pages: yes
good typography: not native, but doable
extensible (without much pain): excellent
reader comments: not native, but doable
backlinks: not native, but doable
org mode authoring: pretty easy via pandoc (a Haskell project)

In theory, this was the highest ranking choice. I was quite looking forward to Hakyll. Having spent time as a professional OCaml programmer, and being a math nerd, I was excited to learn Haskell while I made my website. This did not go as planned. Hours were lost fighting with the Haskell tooling, which is not ready for prime time whatsoever. Integration is the hardest part of implementing any system, and solving this prolem is what it means for a product to work “out of the box”. Haskell, in trying to move as far as possible from worse is better, has seemingly failed to prioritize the naive developer experience, and I simply could not get the haddock to hook up to the Haskell language server for inspection and documentation in eglot or lsp-modeI could have fired up VS Code like a rational person, though I think this was a language server or package manager configuration issue, and that VS Code wouldn’t have been able to get the docs from the language server either. I don’t know. . This renders working in Haskell unacceptably inefficient, as one needs to constantly look up type definitions to write any code of note. And speaking of developer experience, there’s this:

Some features described in this manual are not implemented. If you need them, please give us a shout and we'll prioritize accordingly.

[cabal documentation](https://cabal.readthedocs.io/en/3.4/nix-local-build-overview.html#nix-style-builds)

So…am I supposed to guess where the documentation is wrong? What’s the point of the documentation, then? I hypothesize that the practical importance of these matters, and indeed their importance above “correctness” for Haskell to be useful, is partly why the language hasn’t seen more commercial use.

What’s there to learn here? I could have included developer experience in the requirements, though honestly I expected the developer experience with Haskell to be good. If anything, the lesson is that ease of integration correlates with adoption, so expect smaller projects to be hard to use. Though I never had this problem with Clojure, Common Lisp, Ocaml, ratpoison, and many other niche softwares. Oh well.

Cryogen (Clojure)

With Haskell/Hakyll out of the running, my next thought was cryogen, a Clojure project. Requirements time:

Requirement: Status
well maintained: not great
static website: yes
blog posts and pages: yes
good typography: not native, but doable
extensible (without much pain): no
reader comments: no
backlinks: no
org mode authoring: no

What cryogen suffers from is, in my opinion, insufficient design and poor architecture. In my experience, this is tragically natural consequence of the REPL-driven bottom-up programming approach which is really feasible in LISPs. But bottom-up is only a good idea for exploratory programming, when you are prototyping what the thing can even be. In the words of Alan Perlis: Everything should be built top-down, except the first timePerlis:15.

Take a look at Cryogen’s code structure. The first thing you notice is the flat structure. Not great, not terrible. But some of the core design choices are bad. For example, you have to choose between markdown and asciidoc, you can’t use both at the same time; there’s no logical reason for this, both are simply compilation pipelines from markup document to html. Also, there’s no built-in plugin system; you have to override the core compiler namespace to do anything usefull, which for a Lisp is a swing and a miss. Cryogen saddens me, as Clojure is my favored screw-around programming environment, but to get what I wantThe primary example being cohabitation of markdown and org-mode. Also, backlinks., I would be cloning the entire codebaseThe code base of Cryogen is, admittedly, small. and rewriting core functionality from scratch. That level of effort is out of scope. F for respects.

Nikola (Python)

Alright, two main contenders down. Who’s next? Python isn’t a bad choice, and I spent some time with Nikola. Let’s look at those requirements.

Requirement: Status
well maintained: yes
static website: yes
blog posts and pages: yes
good typography: not many themes, but doable
extensible (without much pain): yes
reader comments: yes, via plugins
backlinks: no, but (maybe) doable via plugin
org mode authoring: yes, via plugin

I think Nikola is a good system. It has a well-thought-out modular architecture, with most of the core functionality contained in plugins. The user can write their own pluginsIn python, of course., supplementing or replacing almost anything about the program. So why not choose Nikola? Because, as far as I can tell, there’s nothing it can do that Jekyll can’t do better. We’re looking at Jekyll at ~50K Github stars versus Nikola’s ~2K, and yes this metric isn’t good, but also maybe it is good. Regardless, the question of Nikola is: are you willing to learn some Ruby if you want to build an extension? If the answer is yesWe live in the age of LLMs, so there is a right answer., then you might as well switch to Jekyll. Though maybe you stop at Hugo first.

Hugo (Go)

Hugo appears to be the dominant player in the space, with ~84K Github stars, beating out even Jekyll by a good margin. Let’s see how it does.

Requirement: Status
well maintained: very yes
static website: yes
blog posts and pages: yes
good typography: many themes, but most are crap
extensible (without much pain): I guess
reader comments: yes, via plugins
backlinks: yes, via plugin
org mode authoring: yes, but I have complaints

Hugo seems to do some things right. It claims to render from markup very fast, and I don’t doubt itI do doubt the importance of rapid builds when incremental builds are the norm, and who is building these static websites with thousands of pages anyway?. It is well maintained, and has many plugins and themes to choose from. I didn’t bother to look into the architecture due to the existence of the plugin system (called “modules” in Hugo), which seems robust.

Where does Hugo fall short? Well, Hugo is written in Go. I don’t have a problem in this per se, but why does Hugo even exist? Go programmers seemCaveat: what do I know. to have this belief that everything should be rewritten in Go, and Hugo itself, as far as I can tell, is a good example. Another example is Hugo’s go-org library, which is a custom written org compiler (to html). Why on Earth is this a thing? To recall the situation: org itself isn’t specified by a formal grammar (already a problem for parsing), and the de facto specification is the Emacs org parser, which requires all of Emacs to run. Either you can run Emacs in batch mode to process org files, or you an (as I see some bloggers doing) exporting their org to markdown/html interactively, or you can use Pandoc. Why have a third option in Go?

Maybe Hugo exist because people wanted a new Jekyll. Here’s what the LLM says about Hugo’s advantages:

Feature Hugo Jekyll
Build speed 1–2 sec (1000 pages) 30–60+ sec
Image processing Built-in Gems required
Multilingual Native Plugins (not on GitHub Pages)
Custom outputs (JSON or AMP) Yes Plugins only
Shortcodes with params/blocks Yes No
No Ruby dependency Single binary Full Ruby env
GitHub Pages support HTML output only Full (but limited plugins)

Most of this doesn’t matter to me; Jekyll seems to do most of it with plugins, and the big disadvantage seems to be shortcodes with parmaters or blocks, which may turn out to be a big deal, it’s unclear to me. (I think the LLM might be wrong about; investigating is a TODO). What it seem to all come down to is: do you want to Ruby, or do you want to Go? Personally, I don’t see how Go is the right tool for basically scripting a templating engine. So I ended up with Jekyll.

Jekyll (Ruby)

I walked around the Earth, and I ended in the beginning. Sometimes the obvious choice is the right choice, but I will never choose it. It’s the folly of man.

Requirement: Status
well maintained: yes
static website: yes
blog posts and pages: yes
good typography: yes (imo)
extensible (without much pain): yes
reader comments: yes, via plugins
backlinks: yes, via plugin
org mode authoring: yes, via plugin (uses Emacs in batch mode)

It was easy to get Jekyll working, I’m not going to bore you with that. The real choice was to the theme. Some basic searching/LLMing lead me to tufte-jekyll, which seemed to do the job. I vendoredI.e. I cloned it. it into my repository, and then—woe—it didn’t build. Something about outdated function signatures in some library, I didn’t want to dig. So onto the next one, jekyll-theme-tufte. As far as I could tell, jekyll-theme-tufte mainly exists to bundle tufte-jekyll as a (Ruby) “gem theme”, allowing directory separation of the theme assets/logic from your content. Personally, I have the opposite philosophy; let the code and the content live in the same repo, second-order ontologies be damned. So I vendored jekyll-theme-tufte in, built it (it worked), deleted the existing demo content, tweaked a few things, and here we are! Exhausted, but victorious. For now.

How to end this off? Unclear. What did I learn? I suppose that, the question is always “build or buy”, and don’t build unless you have to. I will miss you, Haskell. So it goes in life.

← Back to all posts

How Not to Make a Website - November 15, 2025 - Andrew Erlanger