Featured image of post Moving this blog off Jekyll

Moving this blog off Jekyll

The blog you’re reading used to be a Jekyll site on GitHub Pages, built on the lovely beautiful-jekyll theme. It isn’t any more: it’s Hugo now, published to GitLab Pages. The hosting move rode along with go-tool-base leaving GitHub for GitLab, but dropping Jekyll for Hugo was its own decision, and the more interesting one. Most of the migration was painless. Two bits were not, and they’re the two bits worth writing down.

Why leave Jekyll

Jekyll hadn’t done anything wrong, exactly. beautiful-jekyll is a genuinely nice theme and the site worked fine for years. But it had started to show its age. It hadn’t seen much improvement in a long while, and keeping it building meant staying on older versions of Ruby. Ruby is a perfectly good language, just never one I’ve much enjoyed living in, and I could feel one of those fork-it-and-drag-it-up-to-date afternoons coming, the kind I’d done before and didn’t fancy repeating.

So rather than patch up what I had, I asked the more interesting question: what else is out there? It came down to a shortlist of two, Astro and Hugo. Hugo won, fairly narrowly. Partly I just liked more of its out-of-the-box themes. And partly because it’s written in Go: one portable binary, no toolchain to wrangle, the sort of thing an engineer can drop onto any machine and run without a second thought.

The day every image on the blog tripled

The first proper snag was about where images live. Hugo would happily have let me keep Jekyll’s arrangement, one big /assets/images/ folder with every post linking into it by absolute path. But I’d picked the Stack theme, and Stack leans towards page bundles: each post is a directory, and the post’s own images sit right next to its index.md, referenced by plain relative name. The cover image becomes a resource of the post rather than a file in a shared bucket.

That’s a better model, and I decided to commit to it. Getting there, I managed to make a proper mess. The migration copied the old assets/images across, and Hugo’s static/ directory wanted a copy too, and then I started moving covers into the bundles, and at one humbling point a count turned up every image existing three times: once in assets/images, once under static/, and once in a bundle. A blog with a hundred-odd images had become a blog with three hundred-odd, most of them duplicates nobody referenced.

The fix was to go all the way to the bundle model: move each post’s images into its own directory, rewrite the references from absolute /assets/images/x.png paths to bare x.png, and delete the two shared piles entirely. Once the images lived with the posts, there was exactly one copy of each and the path was obvious. But for an afternoon the repository was a hall of mirrors, and the lesson was to pick the new tool’s model and go all the way to it, rather than carrying the old one alongside it and ending up with both.

The “extended” image that wasn’t new enough

The second one cost me the most time, and it’s the most transferable, so it gets the most words.

Hugo comes in two flavours, ordinary and extended, and the Stack theme needs extended because it compiles SCSS. So I reached for an off-the-shelf extended Hugo container image, wired it into the pipeline, and watched the build fail with an error about a template function the theme was calling that simply didn’t seem to exist.

I spent far too long suspecting the theme, my config, my content. The actual culprit was a version. The image I’d grabbed was a couple of minor releases behind, and Stack v4 uses .Site.Language.Locale, a Hugo feature that only landed in 0.157. The image was older than that, so the function genuinely wasn’t there, and the error was telling me the literal truth in a way I wasn’t ready to hear. “Extended” had told me the flavour was right and lulled me into not checking the version, which was the thing that actually mattered.

The fix was to pin a specific, recent extended image rather than trusting a floating “extended” tag to be new enough. The pipeline now runs on a pinned hugomods/hugo:debian-git-0.161.1, comfortably past the 0.157 the theme needs, and the build that had been failing on a missing function went green the moment the version was right. A theme has a minimum Hugo version the same way any dependency has a minimum, and “extended” is a feature flag, not a version number.

What it comes down to

Moving this blog from Jekyll to Hugo, and from GitHub Pages to GitLab Pages on the way, was mostly a pleasant afternoon, with two frustrations worth sharing. Commit fully to your theme’s page-bundle model rather than dragging Jekyll’s shared-assets layout along beside it, or you’ll briefly own three copies of every image. And pin your Hugo version explicitly, because a theme needs a recent enough Hugo, and the “extended” label tells you nothing at all about whether yours is.

If the site renders for you now, both got sorted. If it doesn’t, well, you’re reading this in a text editor, and I’ve some more debugging to do.

Built with Hugo
Theme Stack designed by Jimmy