In June 2025 I moved this blog from Ghost to Hugo . This post works through why but also how that experience was and what some of the pitfalls were.
Why#
There’s not really one big reason for the move, but the combination of several smaller reasons did eventually push me to have enough momentum to make the shift.
There were several reasons pushing me off Ghost, although they’re all fairly inter-related:
- Ghost requires an active server backend. I used to host this blog on
DigitalOcean
using their pre-baked
Ghost blog droplet. In 2023, I wasn’t really keeping up with patching
the server and the manual management so I moved to a SaaS solution and
hosted the blog on Midnight
(which offers
hosted Ghost). Midnight have been great, but:
- it costs money…
- and while it comes with some really nice editing features, it’s overkill for my needs. I don’t use the subscription features and I don’t need the online editor. I’m quite comfortable editing markdown myself.
There are slightly more reasons pulling me onto Hugo. And more specifically the experience of deploying Hugo, using the blowfish theme , on Netlify .
I’ve more recently deployed the websites for SQLFluff and for A14K . Both of which I wanted to launch as effectively static sites to give me freedom about how I hosted them. In searching for good static site generators I came across Hugo and was really impressed with it’s capabilities. The original SQLFluff site wasn’t a Hugo site, but it has now been migrated to it now.
Both of the above are on Netlify using their free plan, and the experience has been great. Not just because it’s free (except where I’m paying for metrics), but I get nice additional features like deploy previews.
The modern Hugo framework also had a nicer local development experience including the experience of using themes. I’ve written before about the' more complex nature of having CI for Ghost themes , where in contrast, because arguably Hugo keeps you closer to the code, the experience of editing the theme and adding custom components is much smoother. One neat example of this is that to you can override portions of a Hugo theme, without actually editing the whole theme. The modular nature of it is much more friendly to modifications at that level.
Having the blog content in markdown (which is easy to port) rather than on a server also felt more future-proof and sustainable. There are tons of static site generators which work on markdown files, and so even if Hugo isn’t where I stay for ever, it’s certainly closer what I imagine will be the longer term solution.
How#
Lot’s has been written about migrating from Ghost to Hugo. I’m not going to reiterate all of that here, but the blogs I found most useful were golangbot and the very helpfully linked tool ghostToHugo .
My process was roughly as follows:
Using the Ghost export feature, export the whole of my current Ghost blog to a
.json
file.Run ghostToHugo to export it into Hugo format.
Set up a blank Hugo project using blowfish (which I’m familiar with) and copy in the generated content from the previous step.
The
ghostToHugo
utility doesn’t handle images, but it did mostly leave links to the images hosted on Midnight in the generated markdown files. I used GitHub copilot to write me a short python script to help. I asked it to:- Iterate through all the generated markdown files.
- Move them into their own folder and rename them to
index.md
. - To search each file for links to images, and download those images to the folder for that article.
- To update each image reference to use a
{{< figure >}}
tag which references the now locally saved image file.
This scripted approach worked pretty well for most files. For reasons I don’t understand, some of my more recent posts didn’t make it through the whole process with their images intact, and so there was a manual process at this stage to work through each post and make sure everything was present and correct. It also didn’t handle image captions containing links well.
Now that all the posts are in one folder, with no date reference in the titles it was becoming quite tricky to know when each post was, and which were the most recent ones. I grouped all posts by year, but without an
index.md
page for each, so that they would all still show up on the/posts/
page.
At this stage, I have a basically functional blog working on Hugo. Now begins the fiddly work…
Fiddly details#
As with lots of projects like this, getting 80% of the way there is fairly easy and the last 20% of the project takes 80% of the time & effort. This project was no exception. Some of the nits which caught me out were:
Theme configuration#
I put quite a lot of time an effort in the past into getting the Casper theme for Ghost to work well for the blog, and I wanted to make blowfish (at least on the homepage) look fairly similar. This took a lot of work.
Blowfish supports a custom homepage straight out of the box, but actually getting that homepage to look like I wanted too quite a lot of fiddling. Especially to get the cover image to behave as it did for casper.
One of the nicer aspects of Blowfish is that it also supports light/dark mode straight out of the box, but that also meant I need to create variants of the theme that would work in both light and dark modes.
Blowfish also supports custom fonts very straightforwardly, but only if you use the same font across the whole site. Previously using the Casper theme I had sans-serif titles, but serif body text. After an hour or so of mucking around with configuration I opted to make the new site totally serif, and downloaded the Crete Round font from GoogleFonts. Given my professional site at A14K Ltd is entirely Sans-Serif, this also makes the two feel quite different - despite the bones of both sites being quite similar.
Post configuration#
Ghost does a lot of things without asking including thumbnail images and article summaries. I remember actually finding this quite annoying at first, and Hugo frees me from these constraints, but it does mean I actually have the configure those things. I worked through all of the posts making sure that each had a thumbnail of reasonable resolution, a header image which made sense and summary text that worked with the article.
For articles where I was just reposting either a podcast or a video, I also made
sure I was either using the externalUrl
functionality to link directly out or
the {{< youtube >}}
shortcode to embed the video properly.
I also managed to take advantage of a few features which are much better supported by Hugo like footnotes (citations) and linking multiple articles together in a series.
Revising Images#
Several of my posts didn’t have images, or had really low quality ones. AI image generation has come on a long way since most were written, so I either used AI upscalers to improve image quality, or asked Sora to generate new images for a few of them. They’re a little cheesy in places, but much better than before.
Responsive Favicons#
The Designing Overload Logo is usually either black or white. That means using
a single Favicon
as per the blowfish
default was going to result in a suboptimal experience for at least somebody.
I found a great post on joyofcode.xyz about responsive favicons
,
which I followed by creating as custom favicons.html
layout (which is
supported by blowfish) and adding black and white versions of the icon, each
using the media="(prefers-color-scheme: light)"
(or ...dark
) tag. I didn’t
add responsive versions of the apple touch icon or manifest file, because I
don’t think that would work anyway.
Linting#
And because it’s me I also wanted to have my markdown files fairly well linted including trying as far as possible to confirm to a sensible line length. That was proving very tedious until I found the Rewrap VSCode extension which as a massive help.
External links & redirects#
One of the things that worried me about moving, and in particular a move which would change some of the post urls, was that some of my posts are linked to on other sites, and I didn’t want to disrupt that. ChatGPT was very helpful in pointing out to me that this could be easily handled using Netlify redirects .
What I didn’t know however was how to find which links to move. I didn’t really
want to move all of them because that felt like a lot of work, when in reality
only a few of my posts are likely to be linked to. I eventually found the Google
Search Console, which has this functionality in the Links -> External Links
section where I could find out which of my articles had been linked to.
I though it would have been my Growing analytics on a Shoestring series but it actually turned out to be my post on Modding the Razer Chroma , which seems to do very well on Reddit.
Deployment#
Once I was happy that the site looked good, that the test deploy on Netlify was working and that the redirects all worked, it was time to deploy. Shifting the DNS settings over to the new site was simple, and we’re live.
All that remains is to finish all the articles I haven’t finished writing yet!