Migrating my blog from Wordpress.com to Hugo & Cloudflare
A few months ago, I decided to migrate my personal blog (former location) from Wordpress.com (free plan), where I had hosted it since 2008, to a static website (that you are reading now). The reason was that I noticed the ads really taking over the posts: There used to be a banner ad and ads at the end of the posts. But by accident, I browsed my website without UBlock and I saw there were so many ads in the body of the post. One option would have been to go with the paid Starter plan (currently $5, their cheapest paid offering). Instead, I decided to move to a static website.
I thought the benefits for a static website would be:
- No ads from the host (my primary mover)
- Free hosting
- Complete control over the layout, such as CSS or theming (not possible even on the Wordpress.com Starter plan).
- Map providers other than Google Maps: Most of my content these last few years have been hike reports and I show a map for those. Wordpress.com only supports Google Maps and Google My Maps, which I use to show custom data.
Except for the last point (which I haven’t got around to implement yet), my expectations were basically met, although I think there are some drawbacks compared to what I could have had with a Wordpress.com paid plan. I summarize those at the end of this post.
Here are the steps I took for the migration.
Hosting platform
I considered Github Pages, Firebase Hosting and Cloudflare Pages, which all offer either free hosting or a generous free tier (Firebase). I already had experience with Firebase, since I use their hosting for some apps in my day job.
Static website hosting is a bit generic and it seemed all of them would have been fine for my use case but, even though I wasn’t a user of Cloudflare, I went with it in the end:
- Free instead of a free tier (like Firebase). Although, with my level of traffic, it would have been unlikely to ever hit the limit…
- No limit on what can be hosted on Cloudflare Pages, unlike Github Pages, which prohibits business websites. If possible, I wanted to also setup my main website (also static and previously hosted on Firebase) with the same hosting provider to make it simpler to manage: It also hosts the support pages for my books.
- There are additional functionalities that I though would be useful, like file hosting (R2) with no egress bandwidth fees, which the others don’t have. I ended up using that for hosting my blog images (although only the most recent posts have been converted). Also server-side functions (possibly for comments, but I haven’t implemented it yet), which Firebase has but Github Pages lacks.
I have no complaint with Cloudflare: It works well and has been free so far (even with R2 image hosting: Still inside the free tier). I have also transferred the registration of my domain to Cloudflare: I used Gandi before and I have had no issue with them, but it was more convenient to have one fewer service. After I migrated the blog to Cloudflare Pages, I also migrated my main website (www.vellut.com) to Cloudflare.
Static site generator (SSG)
Those generators take a folder of content files + templates for layout and generate the pages of the website to be uploaded on the hosting provider and served to visitors. With my > 1,000 posts, the generator creates ~8,000 pages because of the pagination and various access paths, like using tags or months, on top of chronological (although most are not accessible using the template I created).
There were so many choices for the generator that I only considered 3 alternatives:
- Hugo, a static website builder made with Go. It seems to be one of the most popular tools for static generation.
- Pelican, a SSG made with Python
- Something I would develop myself
I went for Hugo:
- Easy to install
- It is very fast to rebuild the website
- I liked one of the themes (Ananke). I ended more or less making my own theme, but I had a good starting point.
- I didn’t feel like developing a whole system…
The reason I considered Pelican is that it is made in Python (which is the programming language I usually work with) and I thought it would be simpler to extend and debug. And, indeed, with Hugo, I sometimes got frustrated with the limited template syntax and the cryptic error messages. In the end, I developed some preprocessors in Python to make it generate what I needed before the SSG proper would run, since I couldn’t make it work with Hugo only.
Some notes on Hugo:
- The contents of the blog posts are authored using Markdown and later rendered as HTML by Hugo. It is quite limited in terms of layout, especially compared to the Wordpress.com block editor but I didn’t really use any advanced functionality, so it was not very hard to port the content.
- It is also possible to use HTML directly, so I suppose it can be used if a more complex layout is needed for a post.
- The metadata (what Hugo calls front-matter) is in TOML. It is located in the same file as the content.
- There are taxonomies for organising the posts: By default, tags and categories are included. I have also added the month and year (derived from the date metadata, using a preprocessor I developed).
- It is possible to add structured data to each post, for example the status of the various preprocessing stages
- There is also support for the generation of RSS feeds and sitemap.xml
- It is possible to keep the post and images together using page bundles
Website migration
I had to convert all my blog posts on Wordpress.com to a format usable by Hugo. I used the content export tool provided in Wordpress.com. This gave me the post contents + metadata in XML. I also downloaded an archive (Media Library) with all the images I had uploaded on Wordpress.com (not that many, since I use mostly Flickr for hosting images). Then, I wrote the code to export all of that to Markdown + TOML metadata for Hugo, using the Python markdownify
library. I also did a little cleanup (like adding new lines, transforming the links that still pointed to the old site, some images still used http).
After that, I customized the theme for Hugo I had previously selected. I actually removed many features I didn’t need and put the layout templates along with the rest of the content files (ie not an independent theme). In the end, the Hugo website looks quite close to my Wordpress.com blog: I intend to maybe create a new design but I haven’t got around to it yet.. I also created a few shortcodes for Google My Maps and Soundcloud (used in one post).
Since I didn’t manage to do it using the Hugo template language, I also developed a few preprocessors in Python to generate the HTML for the tag cloud and the month / category selectors on the side column + set some metadata fields based on the content (like a post image) for easy access.
Finally, I configured a Github workflow to build and deploy on Cloudflare Pages. Upon the creation of a Github release, the workflow runs the preprocessors on my content, then runs the Hugo generation proper and uses pages-action to upload the files of the generated website to Cloudflare.
All this took quite a bit of time, especially the customization of the template (which could have been avoided I suppose).
One last step was to redirect from Wordpress.com: I created a script that used the Worpdress API to update all the posts. It removed the original content and replaced it with a link to the new post. It was easy enough, but in the end, I don’t think that was a good way to update the location of my content (see the last section of this post) since the new website lost all the Google ranking that was gained over the lifetime of the previous one…
Creating new content
One of the nicest things with Wordpress.com is the blog editor. It is easy to write, edit and publish from anywhere. It is not nearly as convenient with Hugo and Cloudflare.
For creating a new post, I first create a Markdown file in the content folder of my Hugo website, using what Hugo calls an archetype to fill in some default metadata.
To edit the post, I currently use Obsidian, which is a personal note taking and organising application. It runs natively on macOS and other platforms. It uses Markdown as a text file format and can edit it WYSIWYG-style so it is useful to author content for Hugo. It is a bit minimalist though, for example no formatting toolbar (although there are a lot of plugins so probably, there are some that can help). I also tried the Markdown editor in Visual Studio Code but it is not WYSIWYG and a bit clunky to use (especially for my use case where I need to reorder images or add legends). For non-image posts, even a simple text editor would possibly be enough but I don’t write many of those.
If the post is a hiking report with a lot of images, I select the images using a Firefox plugin I also developed: I open Flickr image pages as browser tabs and the plugin gives me the URLs so I don’t have to copy/paste. I then have a Python script that resolves them to actual images (the Flickr API has to be used for that) and generates the Markdown inside the post, with the links and titles already set up. I already used that workflow with my Wordpress.com blog but it generated HTML then that I copy/pasted into the Wordpress editor.
After selecting the images and writing the content, I transfer the images to R2 and run various cleaning scripts locally. I also check using a Hugo development server that it looks OK. Then I commit and push to Github. Finally, I create a release using the gh CLI tool, so that Github runs the workflow to deploy the static website on Cloudflare. After a few minutes, it is available at https://blog.vellut.com.
Verdict
The new website has been live for a few months and I have published quite a bit of new content to it. I like the result and the objectives I had were satisfied, although it took more time than I thought to get there. I can say I regret switching maybe a little since it feels like a lot of work and a few compromises just to save $5/month…
Here are the pros:
- Free and with no ads and with a custom domain
- Easily scriptable: The content is just Markdown files
- Easily customizable: Both on the editing/publishing workflow, as well as what is rendered to the user (although I haven’t done everything that I wanted yet, like better maps)
- All the functionalities I cared about have been ported from the dynamic Wordpress.com
Here are the cons:
- The editing and publishing is not as easy as Wordpress.com. I have set up a desktop computer with all the tools needed but it is not very convenient to publish from elsewhere.
- Also, it needed quite a bit of programming know-how to make the whole workflow bearable.
- This is probably my fault but I lost all my Google ranking: As I mentioned above, I simply replaced the content of the old posts with links to this website. I thought Google would work its magic. However, the old website has disappeared from Google but the new one doesn’t show at all in normal search… According to the Google Search Console, it is indexed (although not completely), but it only shows on the Google results by appending the
site:...
instruction (which is what I use for search).- I guess I should have paid the $5 to Wordpress.com for a few months and set it up with the custom domain blog.vellut.com in order to signal the change. Only then, I would have switched to the new Cloudflare-hosted website while maintaining the URLs. It is not used for business so it doesn’t matter much, but think of all my great content people are missing out on!
- Search is clunky: On Wordpress.com, it is a database search. With my static site, I use a redirect to Google.com with
site:blog.vellut.com
appended to the search terms. I considered Google Programmable Search Engine. It works well, but the free version is covered with ads. I also got a quick look at Algolia but haven’t tried it yet. I would ask any unlikely reader to offer additional suggestions but… - There is no commenting system. Not sure if this matters really: My previous blog had them but only a single post had more than a handful of comments and > 99% had none at all.
- Also, it doesn’t have related functionalites like Pingback, but it is not a very big deal. Again, almost none of my posts were ever linked from anywhere.