Well, I finally did it. Ghost has been sitting on an older version for a long time, my certs were expiring, and I did something about it.


My blog engine and hosting were deteriorating fast, so I moved to GitHub Pages, Hugo, and Static Site Generation, (mostly) without losing or breaking anything.


Years ago I moved from WordPress to Ghost. This let me get on with writing and not worrying about the hosting. I moved it to Azure and even added a dynamic SSL certificate rotator using Lets Encrypt.

Eventually, the cert rotator broke, and the blog engine got enough major versions behind to make the upgrade path non-trivial to fix. Not to mention Google Ads decided my site wasn’t good enough for them and so disabled any ads that could have shown up. Hosting costs on Azure, while small, were a factor to think about too.

Around the time I decided to do something about it, I also started paying attention to other people using static site generation to host their blogs. It didn’t take long to find a few really great articles that showed how simple this was going to be. The simplest of which seemed to be Hugo on GitHub Pages. So that is the path I went down.

What went well

My initial research was well-founded, and I very easily found the tool jbarone/ghostToHugo designed for the job.

Initially, everything was super simple, and I had my content in Hugo in under an hour. Mainly I was following youngkin’s article: Create a Free Blog Site Using GitHub Pages and Hugo, and then using ghostToHugo for my content.

(I’m not repeating details here, because youngkin has it nailed.)

Getting this running on GitHub Pages was easy following that article too. Even setting a CNAME and DNS (a temporary subdomain while testing of course) was nice and easy to get working. (I use DNSimple.) Job done. Right?

What went wrong

RSS. My RSS feed has been a bit of an issue really. I knew it would probably break, and it wasn’t even available out of the box on Hugo either. So I fevered away adding RSS generation to the site generation and getting a fair approximation of my old feed. I knew it wouldn’t be perfect, but I learned a bit about how the templating engine worked along the way. And when that hot cutover day came, it certainly failed. I had a few bugs to fix and all 50 people who have added this blog to their feed probably got a bit of a noisy mess. But hopefully, as they now read this article they now understand why. Also, there are now sub-categories with sub-category RSS feed URLs linked to from the right-hand sidebar now.

Custom Theming. I built my custom theme way back when I moved to Ghost. And I didn’t really feel like losing it. So I painstakingly mapped it from the Ghost templating engine to the one Hugo uses. This sucked. and it took ages (weeks) to get it right. Along the way, I probably did make some improvements that will be valuable later, like fixing the sidebar and better image handling possibilities. But this was probably the most time-consuming part of the whole process. In the end, retrologs’ Creating a Hugo Theme From Scratch was super useful at figuring out what went where, but I don’t actually understand some parts of the collection lookup code I wrote in some places yet. But it worked. At the very least, my CSS appears to have held the test of time (surprisingly!).

Permalinks. Turns out Ghost had a few permalink redirect capabilities. And also the default URLs in Ghost were /slug-for-page/ while Hugo is /post/slug-for-page/. Luckily the Aliases feature in Hugo allowed me to add both the draft/preview link (/p/<GUID>/) as well as the old URL so that hopefully, no one loses my content, and instead get bounced to the right place.

Disqus. See the comments at the bottom? I’ve been using Disqus to make them available. Turns out this was quite useful in moving to a static hosted solution. For the most part, this went well, because Disqus actually have a migration tools feature available to use to move the URL associated with a page. The what went wrong part was that I prepared all the pages that looked like the normal post URL (/slug-for-page/) before I cut over my DNS, but didn’t map any of the preview URLs (/p/<GUID>/) across. I think what happened is that Ghost provided an id to Disqus as well as a URL, and it uses the first URL it sees as the identifying one. When I preview drafts, that preview URL is the one it saves. Since Hugo is just using the actual URL and not the id anymore, I lost my mapping over a few pages which I neglected to migrate. (My incorrect assumption was the draft ids didn’t have comments 😢.) I was without comments on some pages while I remedied that across a couple of evenings. It wasn’t too bad to put together a new CSV file for that though in the end.

Google Ads. Turns out they are still not happy with my website (and neither am I to be fair) so there is a bit more work to get them reenabled. It barely makes dollars a month, to be honest, but with it not working it makes a lot less of course.

Content. I spent far too much time looking at my content. While Ghost had tags, Hugo has both Tags and Categories. I went through every historic post (193 over 13 and a half years if you can believe it) and updated many tags (deleting a bunch, adding a few) and creating categories for my content. I could have spent days (and still might) tidying up some of the content that never had any attention since coming out of WordPress and getting auto-converted to markdown all those years ago. But I held back, since I wanted to actually get this process done and back to writing. There was a little bit of work migrating some of the image functionality over too, but for the most part that was fine, too.


Would I choose to do it again? Yes. I am certainly in a better place. So far, I think I’ve backed the right horse in Hugo. The workflow seems about as good as it was before, and I am aware of a few tools such as CloudCannon for better CMS management workflows if I desire. I might miss the Ghost app for drafts, but I mostly write on my desktop anyway. And since it is in GitHub, anything that can commit markdown to there is a way to draft.

Now that I have that done, the next step is likely to repeat the whole process with csmac.nz as well and fix its certs, ads, and content. (That last one has been bugging me since I moved to Ghost to be fair… ¯\_(ツ)_/¯)