On and off over the last few weeks I have been migrating this site from a Django app written by me to a static site generator called Hugo. If you don't recognise at least one of the technical words in the previous sentence, then you won't find anything interesting to read here. But if you find anything that is broken on here, that is why, and you might want to let me know.

Migration wasn't hard

I had to move all my old content into the new site while not breaking any links. I used Markdown for all the content in the old Django app (in case I wanted to do something like this some day), so there was not much conversion required there. I had my own special snowflake method of handling images and YouTube videos, which required a little work to convert.

The core of the migration was 161 lines of Python code (and then another 20 lines of throwaway Python to fix my breaking all the OpenGraph images). If I had entire days to spend on it and an unlimited attention span I might have finished the exporter in a day. I didn't have that, because I have a day job and a stupid car project and a mind that says well I'm going to have to Google this thought unrelated to any particular thing I am doing, but it was much more work thinking about than doing.

The slightly harder part was learning what I needed to learn about Hugo and doing a basic theme for it, but all of that might have been only a couple of long days - if, again, I had been focused on it.

After the migration, I rather wish I had used Hugo from the start. As said, this site was a Django app, because Django is what I know; I figured I would more time fiddling with Hugo (or some other static site generator) than I would writing my own Django app. But I definitely spent far more time writing tests for my Django app than I did migrating this to Hugo!

Authoring is nicer

I like writing things in my favourite text editor. It seems a more obvious way to write, because that is what I do with most of the rest of my time at a computer. Actually, that was partly the way I was writing already; I was writing posts in a text editor, then copy-pasting into the Django admin interface when I was finished. And if I needed to do non-trivial edits I was copy-pasting it into my text editor, editing it, and then pasting it back.

This is not because Django's admin interface is bad. Although it wins no beauty contests, it's rather better than most web-based interfaces, because text boxes in the Django admin behave as text boxes have in your operating system for about 30 years, rather than the fallout of React meeting some UX designer's reinvention of "type text into a computer". But to those people for whom "favourite text editor" is a valid sequence of words, writing text into a web browser is not as natural as working on files in one's favourite text editor.

As with all static site generators, Hugo's master copy is just a bunch of text files. A bunch of text files can be version controlled with Git, which is also what I do with everything else I write at a computer. That, too, is what I do with anything else I write on a computer, and so it's a better way of working for me.

It's much less maintenance

This is the real reason for the upgrade; I wanted less work for myself in the long run. As said, Django is what I know, because it is my day job. I like it a lot! I just have little interest in maintaining a Django app when I am not being paid for it.

It's easier to secure a static site than it is to secure a Django app. My web server is just serving some files from disk; it's nothing the unattended upgrades of my OS can't handle. Being a responsible citizen requires that I kept the various non-OS dependencies of a Django app up to date. That became a chore I didn't want, and if I'm honest I ended up getting Dependabot email alert blindness.

OS version updates were a source of dread. Serving static files does not change much, if at all, between major operating system versions. Serving a Django app does. That requires, invariably, a major Python version update and a PostgreSQL update as well.

Backups are easier

My old site was backed up multiple ways. Snapshots of the entire virtual machine (what Digital Ocean calls "droplets") were made daily, which cost money. That they would restore to a usable site was a matter of faith. I also periodically pulled the database and media files (such as images) to my local machine, which in turn is backed up elsewhere.

The entirety of the source code of this site is in a Git repository. That includes the theme, and all of the site's text and images. Simply by virtue of how this is deployed, that makes at least three up-to-date copies of the site in normal circumstances: one on my computer, one on GitHub, and one on the server. I'm getting those "for free", but then my local machine gets backed up, which makes four.

I also have the statically-linked Hugo binary checked in to the repo. This gives me some faith that, so long as I am on an x86-64 Linux machine, I will always be able to rebuild the entire site from source in a few seconds and re-deploy to a new server very quickly. This means I am more likely to have backups that can be restored.

Dear God it's fast

Really really fast.

It should load just about instantly on any device and any Internet connection that is likely to be in use. The technical people out there will say "of course it does", because of course it does; it's serving small files from a solid state drive.

The old site wasn't slow. I know how to make Django sites fast, and I did. But, without adding more piles of caching on top, it will never be as fast as directly serving files from a disk.

I decided to make that even faster, by inlining all of the CSS for the site on every page. It adds less than two kilobytes per page and saves a render-blocking HTTP request. Fast internet connections won't see this difference, but slow mobile ones will. I also added some magic tricks which make every link within this site to be prefetched, so once you actually click on it it'll load even faster.

"Faster" - of the "just serving file from disk kind" - also means I need fewer computing resources to serve it, which means...

It's slightly cheaper

...I could run this on the same host as a bunch of other static sites. Previously it was running on its own virtual server, because I figured a Django app would need it. Virtual servers are so cheap these days that they may as well give them away with Happy Meals, but that's still a few quid saved. I could reduce that to near-zero if I used S3, and actual zero if I used GitHub Pages or some other free host, but I like having a server I control.

Arguably the reason the Django app was maintenance in the first place was because I like having servers, rather than some Docker container running in some platform-as-a-service that I don't entirely understand. As it was, when it was time to upgrade the operating system on my server, it was also time to upgrade the database along with it, and also the Python version. The trick here is that when I upgraded the OS on my desktop I had to do the same thing on the server, and vice-versa. That could have been made easier by using a managed database, and a Dockerised Python. If I was doing this as a job I probably would. For something I am having fun with I do not like that loss of control.

I did a bunch of other things while I was there

I have some JavaScript on this site. It's used to handle the privacy-respecting, no-script-friendly YouTube embeds, such as this one. It was originally written in Vue, version 2. That version of Vue went out of support a while ago.

I didn't really want to maintain a JavaScript build system anymore. It felt like effort, and it turned out I could replace it with not many more lines of plain JavaScript than it was in Vue code; it's so small (like 500 bytes gzipped) that I didn't even bother minifying it. I can count on plain JS working in browsers for more-or-less ever. I can't count on a JS build system still working on my machine next year, so I do not feel much like maintaining one for a personal project. This fits the plan of making less long-term work for myself.

Not installing anything from NPM means I don't have any linters anymore. I don't feel good about this, but I feel far worse about the utterly insane dependency tree of ESLint and stylelint, and only slightly less bad about some of the insanities of JSLint.

I did a minor visual revamp. The home page now lists every post, which means you can find any post by using Ctrl+F, and also go to all of the categories immediately. And the whole site now uses a monospace font, because I are programmer.

I could have done these things earlier, but I didn't. They weren't meaningful enough differences to be worth doing by themselves.

Hugo is really good

I like Hugo. Actually I tend to like anything that comes out of the Go programming community; it tends towards generating very high-quality software.

I looked into Hugo in the past, and liked it a bit. As I recall its templating system was not as good back whenever that was as it is now. It, and I may be misremembering this, had nothing that would allow one to override blocks of a base template and only allowed including other templates, which made it feel nerfed compared to Django or Jinja2 templates. I like it much better now than last time I looked.

Hugo is impeccably documented and every maintainer should aspire to writing documentation this good.

Hugo builds to a standalone executable, which should continue to work on any of my Linux boxes into the indefinite future. I have this checked in to my repo via LFS just in case.

Anyway

It all works! Please let me know if you find something broken.