From Jekyll to Hugo
Motivation
After seven years, I decided to write a blog post again, only to find that my old toolchain hadn’t aged well. My blog was built on Jekyll, specifically the GitHub Pages gem. Over time, as I continually updated my local Ruby installation, I had to update the blog’s gems as well. After falling down a rabbit hole of troubleshooting gem and theme updates, I realized that GitHub Pages doesn’t keep up with Jekyll either. I could have made it work with Jekyll alone, but it felt like the right time to switch to something leaner and less likely to break over time.
I ultimately chose Hugo. It’s fast, feature-rich, and doesn’t require Ruby, a JavaScript runtime, or any extra dependencies.
Theme
My previous theme was still functional, but over the years, I became increasingly frustrated with the modern web. Inspired by the Bear blogging platform and its no-nonsense philosophy, I decided to strip away external fonts, analytics, and the unnecessary fixed sidebar.
I also wanted a theme that respects the system’s light/dark mode settings for better readability. I settled on hugo-bearcub. It offers more than I need, but I had some issues with the more minimal hugo-bearblog. I might revisit it later to simplify things further.
Posts
In Jekyll, I had a custom permalink configuration:
1permalink: /:year/:month/:title.html
Hugo uses a different default permalink structure, but I wanted to maintain my old links. While I could have used an alias (redirect) in the front matter, I decided against it—switching tools shouldn’t force me to change permalinks. If I migrate to another static site generator in the future, I’d have to juggle multiple URL schemes, creating a convoluted mess of redirects.
Instead, I configured Hugo like this:
1# [...]
2# Displaying the .html extension is considered "ugly" by some
3# Note: Order matters! This should be defined at the top, above `menus` and `permalinks`
4uglyURLs = true
5
6[permalinks]
7 posts = '/:year/:month/:slug'
8
9[outputFormats]
10 [outputFormats.RSS]
11 mediatype = "application/rss"
12 baseName = "atom" # Preserves the `atom.xml` name from Jekyll for the RSS feed
In Hugo, embedding raw HTML in Markdown isn’t allowed (or recommended), so for
some posts, I renamed file extensions to .html
to render them as-is. For others,
I rewrote snippets in Markdown or used shortcodes.
Speaking of shortcodes, Hugo only interprets variables and functions in templates, so I had to use them for handling assets in page bundles:
- Displaying images using the imgproc shortcode
- Linking to a resource within a post, such as a full-size image or a downloadable
file
See code
1{{- /* 2 Renders an absolute path to a page bundle or global resource for use in an <a href> attribute. 3 4 @returns template.HTML 5 6 @example 7 8*/}} 9{{- with $.Get "path" }} 10 {{- with $i := or ($.Page.Resources.Get .) (resources.Get .) }} 11 {{ .RelPermalink }} 12 {{- end }} 13{{- end }}
This workaround seems necessary because of my custom permalink structure.
While a bit more complex than I’d like, this approach keeps post assets self-contained, avoiding reliance on external storage or a CDN.
Hosting
I’m grateful for the work GitHub has put into GitHub Pages—it served me well for many years. For now, I’ll continue using it for building and hosting this site.