Migrating from WordPress.com to Hugo on GitHub + Cloudflare

Last month, I left Automattic (the company behind WordPress.com) after about 4.5 years of working there as a data scientist. As I am moving back into independent consulting, I decided it was time to give my website a facelift and start posting more often. The biggest part of the facelift was migrating off WordPress.com – I now use Hugo for site generation and GitHub + Cloudflare for hosting. This post summarises my reasons for switching and some technical choices I made, which may be useful for people who are considering a similar migration.

Why switch from WordPress.com to Hugo?

The easiest short-term choice would have been to stick with WordPress.com and spend more time on publishing new posts and working on other projects. However, if I were to start a new personal site today, it’s unlikely I would choose WordPress.com, i.e., not migrating would have been due to inertia. Given that I had the free time to invest in the migration, it seemed worth doing for the following long-term benefits:

  • More control over the styling, content, and layout. On WordPress.com, I was on the AU$60 / year Personal plan (which I got for free while working with Automattic). Adding custom CSS requires upgrading to the AU$120 / year Premium plan. More advanced customisation via WordPress plugins requires paying for the AU$396 / year Business plan. With Hugo, I have full control over the website’s source code – for free. Indeed, it felt liberating to customise my chosen Hugo theme and eliminate inline styling in old posts (which I previously used to work around the custom CSS limitation on WordPress.com).

  • Better editing experience. Since 2014 and through the years when I published the most content, I used the Classic WordPress editor. Not being a fan of heavy WYSIWYG editors, I used to edit posts in HTML mode and focus on the content with minimal markup. Since December 2018, WordPress has shipped with Gutenberg as the default editor. While it is possible to use the Classic editor as a block within Gutenberg, I find the experience too clunky. And I’m not the only one: As of November 2021, the Gutenberg plugin has an average rating of 2.1 stars (including many recent one-star reviews), and the Gutenberg repository has over 700 open bugs.

    Moving to Hugo means that I’m free to write my posts in Markdown or HTML using any offline text editor, and experience no surprises when my posts are published. As a bonus, you can see the source of this and all other posts on GitHub. Given that plain text files have been around for far longer than WordPress and Gutenberg, the approach of relying on Markdown for this website is likely to continue working well for decades, even if I end up replacing Hugo. And for a bit of fun, going with Markdown means that GitHub Copilot can try to help with suggestions that range from laughable to eerily insightful.

    GitHub Copilot trying to help with this post

    GitHub Copilot trying to help with this post

  • Platform-independent follower list. On WordPress.com, about half my followers subscribed via email. The other half used the WordPress.com Reader. Reader subscribers can only be ported to other WordPress sites. With the migration, all subscribers join a single mailing list that is easy to port across service providers.

  • Lower running costs. Hosting a site with a custom domain on GitHub Pages is free, but mapping a custom domain to a WordPress.com site requires payment. While the plan cost is low compared to the time cost of switching, it’s nice to eliminate recurring payments to WordPress.com.

  • Learning opportunity. Not being a web developer, I don’t follow changes in the web development world too closely. Taking more ownership and control over my personal site means that I have to refresh some of my knowledge, i.e., the time spent on the migration wasn’t completely wasted on mindless work.

Naturally, I identified some potential risks: Hugo is younger and more likely to be abandoned by its developers than WordPress, maintenance tasks may end up being too time-consuming, and I might miss some features offered by WordPress.com. Ultimately, I decided that the benefits outweigh the risks, which was just the first in a string of decisions on the journey to move off WordPress.com.

Decisions, decisions… (or: solution components)

WordPress.com is an integrated solution, where many useful features are included even on the Free plan. As such, I would still recommend it for people who are less technically inclined, or to those who aren’t interested in fine control over their website. A good way to appreciate what’s included in WordPress.com is to try to migrate an existing site off the platform. I found the process a bit overwhelming at first, but ultimately I persevered and ended up with the following solution components.

Site generator: Hugo. The biggest change was in the site generation approach – from the dynamic WordPress to the static Hugo. This switch makes sense for my website: I write a new post every once in a while, and it remains unchanged for years. Hence, the same content gets served tens of thousands of times. Moving to a static site generator obviates the need for a traditional database – my posts are simple Markdown files that Hugo turns to HTML. Together with a bit of CSS and JS, that’s enough to serve the same content forever.

Of the many static site generation options, I chose Hugo because it seems popular and well-maintained, and because I find its focus on speed attractive. I also like that it’s simple to install and deploy to many hosts, and that its documentation is clear and comprehensive.

Hugo theme: PaperMod. When initially testing Hugo, I went with the Ananke theme from the quick start manual. Then I switched to Beautiful Hugo for its built-in Staticman comment support. When I realised that this support is limited and easy to mimic in other themes, I switched to PaperMod after seeing it on Dan C Williams’s site. PaperMod has a few quirks, but it’s easy to override anything I don’t like (see my tweaks on this site’s repo).

Host: GitHub Pages. I wanted to avoid opening new accounts where possible, so hosting the site on GitHub Pages was a natural and safe choice: It’s backed by a massive company and has been free to use since 2008. I also like GitHub’s sustainability policy, though this should be the standard – any tech company can and should get to at least net zero this decade.

DNS, CDN, and more: Cloudflare. I’ve used Cloudflare in the past and was impressed with the range of high-quality services they provide for free or for a low price. Therefore, making Cloudflare the DNS and CDN provider for this site was a no-brainer. I’m also planning to use it for my domain registration, as Cloudflare now provides registrar services at wholesale prices. On the sustainability front, Cloudflare is committed to powering its network with 100% renewables – it’s going in the right direction, but it’s not as clear on Scope 3 emissions as GitHub.

Comments: Static display + GitHub issues. By far, the most annoying part of the migration was settling on a solution for comments. The Hugo docs suggest Disqus as the default, but they also note many other options. With about 150 comments over nearly eight years, this site is hardly a vibrant discussion forum – using Disqus feels like an overkill. After a bit of research, I learned about Staticman, which can be self-hosted to turn every comment into a static YAML file that gets rendered by Hugo. I liked the static generation aspect of the Staticman approach, but I didn’t like the idea of complicating things by running another service. Therefore, I settled on my own comment layout and stylesheet, which includes buttons to add new comments as GitHub issues. For this, I found the posts by Khalid Yasoob and Dan C Williams helpful, though I deviated from their solutions.

As I was already moderating comments on my WordPress.com site, I doubt that the additional overhead of manually turning issues into YAML files would be unmanageable. In any case, I can iterate on my solution by adding issue templates and automating the conversion of issues to YAML. Other than the added processing overhead, a downside of my approach in comparison to Staticman is that it requires commenters to have a GitHub account. Given my audience, I think it’s a reasonable requirement, and it should help mitigate spam. In any case, I’m not married to this solution – I can always switch to Staticman, Disqus, or any other commenting system. That’s the beauty of gaining control over my website.

Contact form: Google. I had a WordPress.com contact form on my About page, which I replaced with an embedded Google Form. As Google Forms don’t have built-in spam protection from anonymous users, my form requires users to log in to Google. This limits options for potential contacts, but I’m also contactable via LinkedIn or GitHub. While all these options require an account, they’re free and backed by companies that are serious about fighting spam. And of course, Google is a sustainability leader.

Mailing list: TinyLetter. As TinyLetter has been around for years and is owned by MailChimp, it feels like a safe choice for managing my current email subscriber list (unless it grows beyond TinyLetter’s limits). In any case, porting an email list is easy, as no one owns email. Unfortunately, I’m unsure about TinyLetter’s sustainability, but with Mailchimp’s recent acquisition by Intuit, I hope it will be covered by Intuit’s ambitious sustainability goals.

Analytics: Cloudflare. I considered installing Google Analytics, which I didn’t have on my WordPress.com site because it requires a Premium plan. However, I decided against it given the prevalence of Google Analytics blockers (especially among tech-savvy audiences). Taking a bit of my own advice, I asked myself why I needed analytics? The main reasons are: Verifying the site works as expected, and getting a broad idea of where traffic is coming from and which posts are popular. For this, “accurate” view counts are unnecessary, as is close tracking of individuals. Therefore, I went with the lightweight web analytics provided by Cloudflare, which doesn’t collect personal user data. In some respects, it is more limited than the free stats offered by WordPress.com, e.g., Cloudflare’s data retention period is 30 days. But since my focus is operational, I don’t need to retain stats from past months and years – they feel like vanity metrics that won’t change my behaviour.

Bad metric

Source: Measuring what matters: How to pick a good metric

Making the Big Switch

The migration process was similar to that described by Yasoob Khalid. Notable changes from Yasoob’s post were excluding the Staticman setup, tweaking the comment conversion script, and importing images into page bundles rather than using the resized images produced by the WordPress-to-Hugo Exporter. Since I had to go post by post to fix various things that broke in the process (e.g., YouTube embeds), I also took the opportunity to manually clean up the image filenames. Once I was happy with the result, I switched the domain mapping on GitHub and Cloudflare, left a note to followers on WordPress.com, and started monitoring traffic via Cloudflare Web Analytics.

Overall, I’m satisfied with the result. The new layout feels much lighter and less cluttered, but it’s also enriched by features like a dark mode toggle. Lost functionality includes Like buttons, “reblog” options, and the top and bottom menu shown to logged-in WordPress.com users. But these bits feel superfluous – people can still like my posts without a Like button.

Before and after look of a recent post

Before and after look of a recent post

As I was eager to finish the initial migration, I avoided spending too much time on non-critical tasks. These include following all the SEO best practices, increasing page speed, applying various style tweaks, and other small changes. With more control over my site, I now have the power to incrementally address such tasks over time.

In summary, I found the migration rewarding and educational. It was also fun to go through old posts and get motivated to publish more frequently. I’m looking forward to shifting my focus to the content – stay tuned for new posts!


    Public comments are closed, but I love hearing from readers. Feel free to contact me with your thoughts.