Moving my blog to Hugo

Posted by Graham Wheeler on Saturday, April 30, 2022

I have been using Nikola for about the past 8 years for my blog, but have been eyeing the development of Hugo and thinking I might want to migrate, and have finally done it. There’s nothing wrong with Nikola; I think it’s actually less work than Hugo because it handles .ipynb Jupyter notebooks very seamlessly, but Hugo is super-fast so you can work in a ’live-releoad’ mode which I like. So this weekend I finally did it. I didn’t take notes as I went, but I think I can reconstruct what I did fairly easily:

First I installed Hugo:

brew install hugo

Then I created a new site:

hugo new site grahamwheeler --format yaml
cd grahamwheeler
git init .

I decided to use YAML as I am familiar with it, unlike TOML, and I think it is trivial to change later.

I then looked at the themes on https://themes.gohugo.io/. There are way too many options, but it seems it is fairly easy to switch later, so I picked Clean White as I liked the simple look and it had most of what I wanted. I added it with:

cd themes
git clone https://github.com/zhaohuabing/hugo-theme-cleanwhite.git

Then I took the sample config at `hugo-theme-cleanwhite/exampleSite/config.toml’ and converted it to YAML with an online converter, and edited it to fit my settings, putting the result in the top level directory. At this point I could run:

hugo serve

and see my basic site.

Next I copied over my old posts to the content/post folder. They already had YAML front matter and most of that was compatible with what Hugo uses. I did have to clean up the date fields to put them in either yyyy-mm-dd or yyyy-mm-ddThh:mm:ss format, as Hugo seems much more fussy than Nikola when it comes to date parsing. Also, Nikola used status: draft for yet-to-be-published drafts, while Hugo uses draft: true.

I also copied over my image files to the static/img folder. I had to change all the image URL references to be have an `/img/’ prefix for Hugo to find them. But at this point I already has a largely working site.

I did use Kramdown’s CSS customization on a lot of my images, to make them float left or right so text would wrap around them. That isn’t supported by the Markdown system Hugo uses, so I changed a bunch of image references to plain HTML img elements with style attributes.

I also use MathJax quite frequently. That wasn’t working, but the fix was quite simple; in the theme folder I needed to edit layouts/partial/footer.html, and add:

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>

In the same file,, immediately afterwards, I also added the script tag for utterances, which I use for comments now (recently moved from Disqus):

<script src="https://utteranc.es/client.js"
        repo="gramster/gramw.github.io"
        issue-term="title"
        theme="github-light"
        crossorigin="anonymous"
        async>
</script>

Some of my posts are in the form of Jupyter notebooks. Those were a bit more work with Hugo. Nikola is notebook-aware and does the processing itself, but Hugo does not. I found a wrapper around nbconvert, nb2hugo which does a decent job. In Nikola the front matter went into separate .meta files; I had to inline that as an initial cell in the notebooks and change the format to TOML (this inconsistency will likely drive me to use TOML everywhere soon). For now the process of converting the notebooks is manual which isn’t ideal but doesn’t need to happen too often. I put all the notebooks in a folder named notebooks, and created a script in the same folder called convert.sh, with the contents:

#!/bin/bash

for i in *.ipynb
do
    nb2hugo "$i" --site-dir .. --section post
done

I will just need to run that every time I change a notebook.

That’s about all that was needed to migrate. I’m pretty happy with the result. I expect there will be some rough edges but probably nothing too serious.

Publishing is a bit more messy. I host using github-pages, and Nikola has a command that will check my sources into one branch and the generated site into another branch and push everything upstream, easy as pie. Hugo doesn’t seem to have anything like this so it’s all a bit of a kludge.

First I renamed my old repo gramw.github.io and created a new one with the same name, and pushed all the content. Then I created a gh-pages empty branch (I’m not sure this was necessary; it may have happened anyway, but I was having some permission problems as I worked this out and wanted to eliminate this as a source of problems):

git checkout --orphan gh-pages
git reset --hard
git commit --allow-empty -m "Initializing gh-pages branch"
git push -u origin gh-pages
git checkout main

I put the two script elements that I was patching in to the footer.html in a patch/footer.html file:

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>
<script src="https://utteranc.es/client.js"
        repo="gramster/gramw.github.io"
        issue-term="title"
        theme="github-light"
        crossorigin="anonymous"
        async>
</script>

and the command line I used to convert the notebooks in a file patch/convertnb.sh:

#!/bin/bash

for i in notebooks/*.ipynb
do
    nb2hugo "$i" --site-dir . --section post
done

Then I created a Github action do generate the content whenever I push to main and push that content to the gh-pages branch:

name: Github Pages
on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
          submodules: true

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'
          extended: true

      - name: Patch theme
        run: |
          cat patch/footer.html themes/hugo-theme-cleanwhite/layouts/partials/footer.html > footer.html
          mv footer.html themes/hugo-theme-cleanwhite/layouts/partials
          sed -i  's/CATALOG/SECTIONS/' themes/hugo-theme-cleanwhite/layouts/_default/single.html
        shell: bash

      - name: Setup Python
        uses: actions/setup-python@v2
        with:
          python-version: "3.9"

      - name: Install nb2hugo
        run:
          python -m pip install nb2hugo
        shell: bash

      - name: Convert notebooks
        run: patch/convertnb.sh
        shell: bash

      - name: Build
        run: hugo --minify --baseURL=https://www.grahamwheeler.com

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./public

And that’s it! It’s a reasonable solution. Except for one bit of nastiness which I didn’t experience with Nikola (where I pushed to the gh-pages branch from my desktop as part of Nikola’s publish step): every time a change gets published, GitHub loses my custom domain settings and I have to go and re-enter my domain on the Settings/Pages section of GitHub. This tripped mme up a few times until I realized what was happening. It’s really annoying but I don’t know that there’s much I can do about it.