I’ve been using GatsbyJS for publishing my blog posts here, but I wanted to move to another static site generator that is more automation friendly (more on this later). That’s why I decided to migrate this blog to Hugo , which has a very active community and is developed with Go. At first, I was scared of this move, since I don’t know how to code in Go, but to my surprise the whole migration process didn’t require me to write any Go, and everything is handled via yaml, html, and jinja.

Here is a summary of the steps required to migrate a blog from Gatsby to Hugo:

Create a new Hugo site

My blog’s code sits in a Github repo and the content is served by Netlify using a direct connection to the repo. Some of my existing blog posts have a decent amount of seach traffic, so I didn’t want to mess up the URLs with this migration. I created a new Hugo site on my local machine and deploed it to a new Netlify subdomain, configured a few things to make sure the migration of the existing posts will be smooth, and then updated my domain to serve the new website.

On Mac:

brew install hugo

Note that you don’t need to have Go installed for this migration.

  • Create a new Hugo site:

Replace mywebsite with name of your website.

hugo new site mywebsite --format yaml

Choose and add a theme

I decided to use PaperMod as my theme, but there are many options you can choose from.

  • Run the following two commands inside the folder of your Hugo site:
git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
git submodule update --init --recursive
  • Add following to hugo.yaml:
theme: ["PaperMod"]

Move the existing content

My existing blog posts on Gatsby had a <domain>/<post-slug> URL format (for example: https://saeedesmaili.com/topic-classification-of-texts-locally/), while Hugo by defauls uses a /posts prefix, so if I just created new posts the URL would be <domain>/posts/<post-slug>. I didn’t want to deal with redirecting the old URLs, and decided to use the url frontmatter param in Hugo to enforce the same URL schema for my existing content (but will use the default /posts/<post-slug> for the new content, as you can see in the URL of this blog post).

  • Define a default frontmatter format for Hugo posts:

Since I just had a handful of existing blog content, I decided to create a new post content on Hugo and copy paste the markdown from Gatsby for every post. Hugo lets you define a yaml frontmatter template for new content, so you don’t need to write title, date, etc for each post you create. To do this, I added the following in a new file called default.md in the archetypes directory:

---
title: "{{ replaceRE `^[0-9]{8}-` "" .File.ContentBaseName | humanize | title }}"
slug: "{{ replaceRE `^[0-9]{8}-` "" .File.ContentBaseName }}"
date: "{{ .Date }}"
draft: false
---

The replaceRE `^[0-9]{8}-` "" .File.ContentBaseName part trims eight digits and a - from the beginning of the post slug. I added this to be able to prepend the post creation date to my markdown file names, but excluding them from post title and slugs. This way the markdown files will be sorted in the directory instead of being all over the place.

  • Then, to create a new Hugo post content:
hugo new content posts/20240201-post-title.md

This creates a new file called 20240201-post-title.md in content/posts/ directory with the following content:

---
title: "Post Title"
url: "post-title"
date: "2024-02-01T11:30:57+01:00"
draft: false
---

I copy pasted the markdown content of my posts to this file, and since both Gatsby and Hugo use markdown, the post content migration was smooth, expect for the images and local files that I need to move as well.

  • Move images and files: The images of Gatsby posts reside in the same directory as the post’s markdown file:
- content/
  - blog/
    - sample-post-1/
      - images/
        - image-1.png
        - image-2.png
      - index.md
    - sample-post-2/
      - images/
        - image-3.png
      - index.md

And Hugo supports this exact file structure as well, but I preferred to opt for storing all the images in one single directory (which I called it images) in the static directory. For each existing post, I moved the image file to the static/images/ directory, and prepended the post creation date to the file name. Then in the post content, using the VSCode’s search and replace feature, replaced /images/ with /static/images/20240201-. Adding the date to the image file name will make it easy to find them in future if needed.

And that’s it! All existing content are moved to Hugo and I could deploy to Netlify, but I also made a few other config changes as well.

Other configurations

  • Rename the RSS file to rss.xml:

The RSS file in Gatsby uses rss.xml name, but Hugo uses index.xml. This means people would need to know about this change, and to update the url in their feed reader, and to be honest I’m not sure how many people are subscribed to my blogs feed (Are you? Porbably not many), but I wanted to keep the old name and retain the subscribers. So I added the following to hugo.yaml:

outputFormats:
  RSS:
    mediatype: "application/rss"
    baseName: "rss"
  • Include the full text of posts in the RSS feed:

As I don’t want to bother the readers who follow me via RSS, I added the following config to hugo.yaml to include the full text of my posts in the RSS feed:

params:
  ...
  ShowFullTextinRSS: true
  • Use a serif font instead of the default sans-serif one:

I like to use a serif font wherever there is a lengthy content to read, so I added a regular, bold, italic, and bold-italic versions of the IBMPlexSerif font to the static/fonts/ directory, then created a file named custom.css in assets/css/extented/ with following:

@font-face {
  font-family: "IBM Plex Serif";
  src: url("/fonts/IBMPlexSerif-Regular.woff2") format("woff2"), url("/fonts/IBMPlexSerif-Regular.woff")
      format("woff");
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: "IBM Plex Serif";
  src: url("/fonts/IBMPlexSerif-Bold.woff2") format("woff2"), url("/fonts/IBMPlexSerif-Bold.woff")
      format("woff");
  font-weight: bold;
  font-style: normal;
}

@font-face {
  font-family: "IBM Plex Serif";
  src: url("/fonts/IBMPlexSerif-Italic.woff2") format("woff2"), url("/fonts/IBMPlexSerif-Italic.woff")
      format("woff");
  font-weight: normal;
  font-style: italic;
}

@font-face {
  font-family: "IBM Plex Serif";
  src: url("/fonts/IBMPlexSerif-BoldItalic.woff2") format("woff2"), url("/fonts/IBMPlexSerif-BoldItalic.woff")
      format("woff");
  font-weight: bold;
  font-style: italic;
}

body {
  font-family: "IBM Plex Serif", "Merriweather", "Georgia", "Cambria",
    "Times New Roman", "Times", "serif";
}
  • Add analytics script:

I use SimpleAnalytics to gather some basic data around which contents of this blog are more useful for people. I created a file called extend_head.html in layouts/partials/ directory and added their javascript tag in the file.

Deploy!

I then created a new website on Netlify, connected the Hugo site’s repo from Github, and after playing around with the website on Netlify’s subdomain and making sure everything works fine, I removed my domain from the existing Gatsby website on Netlify, and added it to the new Hugo one. And with that, the website was live with Hugo!

PS. One of the main reasons for doing all these and migrating to Hugo was to be able to have a new notes section on this blog, and publish my brief notes on what I read and discover every week, in a relatively automatic way (let me know if you’re interested in a write up on how I automated sharing these notes with Readwise , Obsidian , Alfred , and Hugo). These notes are not displayed on this blog’s home page, but will be accessable on the /notes section and this section has its own RSS feed , so make sure to check it out and subscribe to the feed if you’re interested.


Comment? Reply via Email, Mastodon or Twitter.