Building a Website with Hugo

About two years ago I wrote about building this website using server-side Javascript. I used the Gatsby framework to build my website on Node and React. For the most part, developing using those frameworks and libraries was a good experience. But there were a few issues that pushed me to explore other options. Ultimately I landed on a choice that I’d considered and rejected the last time around: Hugo, a super fast framework for creating websites built with the Go programming language. So now this website has been rebuilt on Hugo.

Build Speed

One of the worst issues I had with Gatsby was speed, both in terms of building the site and viewing it. When you use a static site generator, you need to rebuild the entire site whenever you change anything, which is one of the drawbacks to that method. So the time it takes to build matters significantly. Gatsby does a lot of processing, manipulation, and optimization when building a website. Some of that is helpful, but it takes a while: building my (relatively modest) website on my computer took about 3 minutes. That might not sound like a long time, but it adds up, given that it has to happen every time the site changes.

Hugo is much faster. A complete build of the website takes about 1.5 seconds.

Now, a lot of what Gatsby does is useful. It loads and optimizes images; minifies HTML, Javascript, and CSS; and can run some relatively advanced content queries using GraphQL. Hugo doesn’t do the image processing, and its content parsing options are definitely more limited than Gatsby’s. But it does all the minification and provides plenty of functionality to manipulate content.

Gatsby’s sluggishness makes for a poor development experience as well. It takes a long time to get a development server running. And even once the development build is complete, Gatsby still sometimes takes an inordinate amount of time to generate pages when you browse to them. It’s also unstable; my Gatsby development servers sometimes tend to crash. Hugo, by contrast, is lightning-quick to start a development server, it loads pages instanteously, changed pages refresh almost immediately, and it’s yet to crash on me.

Page Speed

Perhaps even more important, my Hugo site loads faster for visitors. PageSpeed Insights is one of the most trusted tools for measuring website speed. It scores pages on a scale from 0 (slowest) to 100 (fastest). My Gatsby website consistently scored in the mid-50s to the mid-60s, which is pretty mediocre. A lot of the delays in loading the page were due to cruft that Gatsby added to my site (more on that below). My Hugo site scores substantially better, in the mid-70s to low 80s.

Gatsby (left) vs. Hugo (right) PageSpeed comparison
Gatsby (left) vs. Hugo (right) PageSpeed comparison

Cruft

Gatsby adds a lot of crap to my website. When it formats my pages and elements, Gatsby adds a lot of wrapper tags, Javascript files, and styles to make my site work. This slows things down (PageSpeed identified the React framework Javascript files as a major blocking resource, for example). These additions also make development tricky. Gatsby adds a lot of wrapping <div>s, both around the general layout of my site and around images that Gatsby processes. To style some of my pages, I had to track down the classes for those elements and override some of the styles that Gatsby applies.

Implementing search on a static website is a challenge. By definition, static websites aren’t database-powered and don’t have a way to run searches on the server. When building with Gatsby, I used Algolia, a commercial solution that stores an index of the content on your site, queries that index in response to users’ searches, and returns the results in a format that you can use to display the results. It works pretty well, and I never came close to exceeding the limits on Algolia’s free tier. But there were some drawbacks as well: using an external commercial solution isn’t all that elegant, and Algolia requires free users to put a “Powered by Algolia” link below the search results.

Hugo provides a list of options to enable search functionality on your website. A lot of them are outdated, and the documentation isn’t very good. Additionally, most require installation of Node modules or other additional software. I started digging into the alternatives and found that several of them relied on Lunr.js, a Javascript module that searches a site index and ranks and returns the results. Fortunately, Lunr.js can be used as a standalone Javascript file that doesn’t require installing anything, and I found it quite easy to create my own frontend for the search interface.

First, I had to tell Hugo to generate a JSON search index for my site. That involved modifying my config.toml file to generate a new output format when building my site:

[outputs]
  home = ["HTML", "RSS", "SearchIndex"]

Then I had to create the template for the search index. Figuring out how to do that required examining the code used by some of the Hugo search options, but I eventually got it and customized it for my site. I created a file in the /layouts/_default/ folder of my theme called list.searchindex.json. The contents of that file are:

{{- $.Scratch.Add "searchindex" slice -}}
{{- range $index, $element := (where .Site.Pages "Kind" "page") -}}
    {{- $.Scratch.Add "searchindex" (dict "id" $index "title" $element.Title "uri" $element.Permalink "tags" $element.Params.tags "section" $element.Section "content" $element.Plain "summary" $element.Summary "date" ($element.Date.Format "January 2, 2006") "location" ($element.Param "Location") "image" (replace ($element.Param "featuredImage") "PHOTO_SIZE" "w300-h200-p") ) -}}
{{- end -}}
{{- $.Scratch.Get "searchindex" | jsonify -}}

That tells Hugo how to create a JSON file containing information about all the content on my website. Then I load that index into Lunr.js (via an AJAX call), and it’s ready for the user to search.

Features

Gatsby is incredibly powerful, and it doesn’t impose any structure on your website. That’s definitely cool in its own way; it creates a blank slate for developers and really lets you build anything you want from scratch. But it also means that Gatsby’s default setup is missing some critical features, like taxonomies (think tags and categories) and even pagination. There are plenty of guides and artices explaining how to implement those features, but they take some effort, and it can sometimes be hard to figure out how to customize things to your liking.

Hugo doesn’t force much structure on you, but it does include basic taxonomies (tags and categories) with the ability to delete them if you don’t need them or to add more. There’s also a basic pagination function that can be customized if you don’t like the default.

Flexibility

Gatsby is definitely more powerful and more flexible in some important ways. Gatsby uses GraphQL, which provides a passable imitation of a database backend. This lets you identify, query, and manipulate your content in almost any way you can imagine. Its development server also creates a GraphiQL isntance for you, which makes it easy to test and debug GraphQL queries.

Hugo uses Go’s HTML and text templating libraries. It’s clunky with a somewhat unintuitive syntax, and it’s not nearly as powerful or flexible as GraphQL. Its error reporting can also be hard to trace, and the error messages aren’t always helpful. It doesn’t have anything equivalent to GraphiQL for testing quries. But for most websites, it’s probably plenty. Once I figured out how to write functions, it worked just fine for me.

You can install extensions, often as Node modules, within Hugo to add some of the features and power that it’s missing. But that seems to defeat a lot of the purpose of using a lighter framework like Hugo, so I assiduously avoided doing so. The entire project folder for this website, including the latest build, clocks in under 8 megabytes. The equivalent website built with Gatsby uses 3.5 gigabytes.

Conclusion

Rebuilding this site with Hugo was fun, and I learned a lot. The result is a leaner, faster website. Development is a much better experience, too, thank to Hugo’s much faster development sever. I was also able to optimize and modernize my code along the way. If you don’t need any of the more advanced features available through Gatsby, I highly recommend Hugo.