Switching from Jekyll to Hugo

Fewer dependencies, better information architecture, something new.

It has been a while since I spent any time on my personal website here, but, recently, I have a few projects and ideas looking for a place to be recorded. As part of revisiting the site, I decided it might be a good opportunity to switch from using Jekyll as the static site generator to using Hugo. Here are some of the motivations and small learnings from that process.


I first started with Jekyll back in 2013 when I was looking for a simple way to publish posts without the need for managing a complex environment, like WordPress. Eventually I decided to use Jekyll since it had a popular ecosystem, supported exactly what I was trying to do, and GitHub would take care of hosting the site on a custom domain for me.

Over time, I started wanting to do more things which Jekyll or GitHub Pages didn’t support. This included things like HTTPS for custom domains (now supported), allowing custom plugins, supporting photo galleries, storing assets outside of the repository. To support those, I ended up self-generating and self-hosting the site. It works, but, despite it being mostly automated, it has quite a bit of overhead if it’s to be properly maintained.

Part of the automation included a Docker image and Gemfile for using the correct Jekyll and gem dependencies to build the site. The combination made it easier to run consistently anywhere, but it was still a fairly heavy dependency to have and keep updated (i.e. GitHub had been showing a vulnerability alert for one of the gems for a while). As part of a general move to lighter dependencies, I thought there should be room for improvement.

Another semi-related component was frontend asset management (like CSS and JavaScript). I never implemented a full asset-building pipeline, but did have some simple scripts to help statically build the assets with cacheable paths. I did not want to spend time with a complex asset build system like gulp, but it would be nice to have some simple, effective asset management.


Similar to Jekyll, Hugo is static site generator. It encourages you to focus on writing content, organizing the content, and maintaining websites easily. While Jekyll is Ruby and gem-based, Hugo is executed with a single, static binary with no other runtime requirements. The minimal dependencies definitely made it simpler when getting started and automating processes.

One of its core concepts is the idea of different content types. Rather than there just being a “blog post” type of content, you can have one type for posts, another type for photos. Each content type automatically has its own theme files for different styling and presentation (which I find simpler than Jekyll’s limited layout support).

Another difference is Hugo’s native support of taxonomies and content sections. Taxonomies can be used to organize content across many content types, and content sections are more of a primary section with a content type. I think this will make it much easier as I experiment with using this site for a few more types of content.

A major difference from Jekyll is its templating system. Hugo uses the standard Go template package for rendering, so, by necessity the templates are very simple. While these templates are much more limited than Jekyll’s ERB templates, it does help avoid theme files become too complex and unreadable.

One feature that Hugo has is basic asset management for CSS and JavaScript. Having worked with several other asset managers in the past I know there can be some complex setup and configuration required, but Hugo’s approach is extremely simple with sane defaults and the declarative nature of the templates. While my site does not have complex assets, it’s nice to support some best practices through the assets.


The process of switching to Hugo was fairly straightforward, although it took several attempts over the course of a few months. There were a few memorable learnings from the process though…

Structure – since Hugo handles content types a little differently than Jekyll, I moved around most of the files to match Hugo’s strict, path-based expectations. Conceptually, archetypes, taxonomies, and sections were straightforward, but it took some experimenting before deciding how I wanted to represent my own content. For example, should a photo gallery be a taxonomy or a section; should those photos be their own type or serialized to data for a gallery; or how to organize private project planning drafts in sections?

Terminology-wise, I did get confused a few times on when the singular vs plural form of types should be used. I think taxonomies should always be plural (even in the content directory if adding _index.md), but that can be confusing if you also have a regular content type (which is singular). It took a couple reads of the documentation pages and some trial and error before I worked it out.

Styling – as part of the switch and refreshing the site, I switched to Bulma for CSS. My reasons for using Bulma were not very technical - there are plenty of good frameworks out there. I picked it primarily because: it was something I was not already familiar with, it used a different style of class naming, it was easy to imagine how a simple site like this one might look, and it was not a framework that I recognized as being overused by many other sites I encounter.

Overall, it worked out for my simple visual goals of the site. After working with it, I don’t think I enjoy its class naming conventions (e.g. has-text-centered and is-italic) because the phrasing and conjugations seem unnatural to me. I did appreciate the range of simplistic building block components that it provides.

Deployment – in the Jekyll version of the site, I had a dedicated Docker image which was intended to build the site. This was convenient, but it easily got out of date because it was not regularly rebuilt. Typically I would end up running whatever version of Jekyll I had on my workstation since I did not want to deal with gem versions or running it within Docker. With Hugo and its single binary, it turned out to be a much simpler dependency to manage and automatically upgrade.

Deferred Work – there were a few things that did not seem worth switching on the initial pass:

  • I intentionally changed some of the URLs while switching. I used the aliases front matter option for the posts, but didn’t go through the effort of recording aliases for existing photos and gallery pages since they’re less of a priority. If Google Webmasters makes much of a noise, I figure I can backfill it with a script later.
  • In general, I did not spend much time on the photo or gallery pages. I’m still figuring out what the purpose and intent of those pages should be. I appreciate being able to host galleries outside of social media platforms, but I’m not sure if it is still worth the effort.

Random Annoyances – there were a few other minor frustrations that I noticed while switching:

  • Auto-rebuild does not always auto-rebuild. Sometimes when updating a theme file, one page would reflect the change but another would not be updated. The --disableFastRender flag eventually helped avoid stale pages.
  • There were a few differences in how Markdown was rendered. I needed to review some of the pre-formatted code sections and list formatting of posts.


It is rare for me to revisit previously-written content for the sake of reviewing it. As part of the switch though, I at least skimmed through pages while I was finding and manually fixing conversion issues. It was interesting to revisit the different problems I have solved and consider how those solutions played out over time. In general, it was a good reminder for me to write posts which mark specific curiosities and discoveries I encounter (even if the writing is not the highest quality). When looking back, it makes it easier to consider how and why my experiences evolve over time.