Migrate to Hugo

Hugo is another popular static site generator that runs faster than Jekyll. Recently, I migrated my blog from Github Page + Jekyll Now to Cloudflare Pages + Hugo.

Motivations

There are mainly two reasons why I migrated my blog from Github Page + Jekyll to Cloudflare Pages + Hugo. First Github requires premium to build and update webpages based on a private repo, Cloudflare Pages can deploy private repos, and the deployment could be on demand that you can push multiple commits to your repo then compile and deploy them all at once via the API (as simple as a POST request to the certain URL). Second Jekyll Now depends on Ruby, while Hugo provides standalone binary which is much more convinient (after I just started running LFS on my machine, packages were managed manually) and faster to build and preview on my local server.

Migration

I would not say migration went seamlessly. Initially, I tried to find configs on both Jekyll Now and Hugo that will build the web site from the same set of files in the repo. At some point, I found that was unlikely going to work. Over the migration, there are a few things that have to be changed for Hugo and will break Jekyll. Eventually, I decided to give up the compatibility for Jekyll. The changes are mainly on the following aspects.

  1. directory hierarchy
  2. file naming and URL indexing
  3. gist plugin
  4. front matter
  5. layout and styling

Some of them involves changing to massive number of files, so I wrote the following two scripts to automate that partially.

Directory Hierarchy

Previously under Jekyll Now framework, there are some certain folders whose name begins with underscore for certain purposes, and now they are mapped to different folders under Hugo.

  • _posts renamed to content: where .md files of posts are
  • _layouts renamed to layouts: where the rendering templates are, Hugo has a bit more complicated rules to determine which templates to use for the rendering of a page
  • _pages: the folder had the file for non-post standalone pages, such as about.md (moved to content), categories.html and tags.html (become templates in layouts)
  • _sass becomes sass under assets: where the styling config files are
  • _includes becomes partials under layouts: where components to be included into multiple templates are
  • assets renamed to static: where the static files such as images are

File Naming and URL Indexing

Jekyll Now infers post dates from the file names (in the format as yyyy-mm-dd-CamelCase.md), while Hugo uses the date specified in the post front matter (header section). Additionally, the generated URLs to index the pages come from the file names and Jekyll Now would keep uppercase letters, while Hugo would convert all to lowercase. Therefore, we need to rename each .md file to 1) strip the leading date; 2) and switch from camel style names to snake style names. A find command with -exec argument (see my earlier post on find) can apply the renaming action to all the .md files. Striping the leading date means dropping everything before the last -, which is accomplished by Line 30 in gitmv.sh. To convert file names from camel style name to snake style names, I write a function camel_to_snake() in gitmv.sh to iterate through the file name, do substitution upon uppercase letters. We need to skip the insertion of underscore on those uppercase letters that is right after another uppercase letter, because continuouse capital letters usually are abbreviations (e.g., HPC for High Performance Computing).

Along with the file name change, the URL in some of my posts that directing to other posts would need changes too. We should drop the leading {{ site.baseurl }} (or use {{ .Site.BaseURL }}). For the URL to static contents (such as images), the assets should also be removed from the path. Lastly, the index to a page will be all lower case in snake style. Line 9 to 12 in migrate.sh accomplish the URL changes.

The scripts do the most changes, while I would need to manually take care of some corner cases, such as TeX, C++ would not be handled in the way I desire.

Gist Plugin

There is a gist plugin for Jekyll Now to fetch public gist and display the code in the specific file of the gist (see my earlier post on gist). The format to invoke this kind of plugin is a bit different on Hugo. See Line 15 in migrate.sh script does the substitution to convert the format.

Front Matter

For Hugo, there are 3 formats of front matter (json, yaml, and toml), and what I previously wrotten for Jekyll Now are valid yaml format, except a couple of subtle differences. There are two adjustments needed for the front matter of each post .md file: 1) date time format; 2) the tag list.

The date and time format recogonized by Huge is yyyy-mm-ddTHH:MM:SS followed by time zone info in the format as +/-HH:MM. Line 18 to 21 in migrate.sh convert the date time format.

Jekyll Now supports automatically breaking the list of tags in front matter by space then attach them to the post. While it is convinient, this means your tag cannot contains spaces. For Hugo, it does not break the line automatically, instead you should put multiple tags each on a different line. Line 24 in migrate.sh breaks the tag line to make multi-tag work with Hugo. It took me a while to understand how sed command repeat editions on the same line iteratively until conditions not met.

Layout and Styling

Web page style is configued by generated CSS file, so mostly it did not change much. The only failed one was the rule for code highlighting. Previously, the code segement would be captured in a div or code block with proper class name, such as begains with language-. However, Hugo generates the DOM tree a bit differently, so the targets need to be specified as p>code, that any code block under a p block.

The templates used by Jekyll Now will work for Hugo after some simple tweak, however in order to make the layout as the same as before is difficult. I ended up rewriting quite a few templates to get a satisfying state.

Credits