The past few months I’ve been in something of a conundrum with Drinking Caffeine. As the post count hit 100, 200, and then 300, Jekyll was increasingly slowing down to a crawl in its ability to rebuild the site. Not just during deploys, but during local builds in edit mode. Every time I would make a change to a markdown file, it would take about 10 seconds to update the local HTML preview. It was getting ridiculous. Having to wait 10 seconds before changes can be verified in a web browser is a deal breaker.
Whilst musing about my predicament last weekend, I considered moving to WordPress. Here were the pros:
- WordPress is powered by MySQL for its user-generated data. Databases were designed to be lightening fast with thousands and millions of rows. This would solve my speed issue overnight.
- WordPress is very popular. It’s responsible for 27% of the web. It has a lot of momentum and it’s getting better all the time. There are great hosting solutions for it such as WPEngine. Lots of tech sites are powered by it.
Here were the cons:
- Like any database-powered CMS, I would lose a number of things with switching from Jekyll. These included:
- Version control via git.
- The ability to edit directly in my IDE of choice. Instead, I would have to return to the pain that comes with interacting with a browser application (or MarsEdit) instead of the much more elegant syntax of YAML front matter.
- Then there’s the concern about security. A statically-generated HTML site has virtually zero concerns in this regard, whereas a CMS with a web login interface exposes this vulnerability; especially when it’s software as popular as WordPress where everyone knows that the login is /wp-admin. Yes, you can change that default URL as well as install 2FA plugins. But the point is that with static, you don’t have to even mess with that, and with non-static, you do.
- Lastly there’s the consideration of hosting costs. Services like GitHub Pages and GitLab Pages let you host static sites on their servers for free. That’s a whole lot better deal than paying $348/year at WPEngine for the WordPress equivalent; especially if your blog is a side project that you do for fun.
I concluded that static was the way to go, but I still needed to find a way to get faster builds locally. I looked into a piece called Increase Jekyll Build Speed to no avail. Even incremental builds didn’t seem to make a difference, likely because I’ve been using things like Jekyll Archives which means that incremental builds can’t really be that incremental.
At this point I decided it was time to find a completely new build system. Googling around, I found this piece by Eli Williamson at Netlify:
Jekyll holds it’s [sic] position at #1 as the most popular static site generator. That’s no surprise, considering it was created and fantastically supported by none other than GitHub.
Since we last reported on the top ten static site generators back in 2016, Hugo has jumped up the list to #2. Built around Google’s Go programming language, it’s blazing fast. This is no accident, it was engineered for speed (massive Hugo sites can be built in milliseconds) - even Smashing Magazine, with a seemingly endless well of articles and knowledge, recently switched to Hugo and experienced incredible reduction in build times and fantastic increases in flexibility. Learn more about their switch here.
Hugo sounded promising, so I explored it more. It turns out that Hugo is faster — much, much faster. This is because unlike Jekyll, which is built upon the scripting language Ruby, Hugo is built upon the compiled language Go. When it comes to file processing, scripting languages are pretty lousy compared to compiled ones, at least in this case. I decided to switch from Jekyll to Hugo. The transition took me a little over 14 hours to complete. The devil’s in the details, the peaks of which I’ll highlight in a moment, but here’s the important part: my local builds have gone down from 10 seconds to 100 milliseconds. That’s a 100x increase in speed. Looking at the Codeship logs for two specific deploys, the production-mode build time has gone from 9.621s to .775s, roughly a 10x increase. Hugo is clearly the victor. I’m never returning to Jekyll.
Whilst saying this, I want to make something clear. I’m not trying to throw shade on Jekyll as Jekyll. Rather, I want to point out the fact that compiled languages, not scripting languages, are the right choice as the underlying architecture for a static site builder. I suspect that Jekyll will remain the #1 most popular static site generator for sometime to become, for the simple reason that GitHub Pages offers it as the only choice on its platform.1 If you’re into the idea of using a static site generator, you most likely use and love GitHub, so it’s a very natural fit. I do not think GitHub will offer support for Hugo any time soon, because GitHub would have to build up a robust Go infrastructure, and its culture is Ruby. Making architectural accommodations for Hugo does not seem to be a wise use of GitHub’s resources, considering that GitHub Pages is a free service. That said, GitLab Pages has native support for Hugo, so competition might drive GitHub to eventually offer similar.
Now, for a few technical details of the transition:
- I had to fix change the sytnax for all of my internal linking. This was a pretty simple search-and-replace of the Jekyll syntax for links versus the Hugo syntax. The hugo import jekyll command was a convenient jumpstart for transitioning the project, but it fell short of many things, and this was one of them.
- I also had to migrate a lot of the URLs of old posts by hand. Jekyll determined a post’s URL by its filename, with the post’s front matter optionally overriding it. Hugo determines a post’s URL by its title, with the post’s front matter optionally overriding it.
- I dropped the hour, minute, and second from my publish time. Hugo botched my dates in the
hugo import jekyllcommand (it assumed my front matter timestamps were in standard UTC timezone, whereas they were in US Central, as specified in my Jekyll’s
_config.ymlfile). Rather than go back and correct all of those by hand, I decided to pick my battles elsewhere and just drop this feature. The exact timestamp was never fully accurate anyway, because it got generated at initial compose time, not publish time.
- I tried using server-side syntax highlighting for my code snippets but pygmentizer doesn’t support all the languages I would like, plus it was buggy in both Tmux sessions and in Codeship deploys. I decided to give PrismJS a chance instead. So far I’m liking it a lot. Especially the new syntax for invoking it — I’m now doing markdown’s standard triple tickmarks instead of the Jekyll-specific snippet highlighting syntax.
- I couldn’t retain my old month-by-month archive — at least, not without a lot of custom work. I decided to go with standard pagination that comes with Hugo.
- I also couldn’t retain my old feed URLs, so I consolidated those to a single URL.
As you can see above, Hugo is limited and substantially more challenging in certain respects that Jekyll is not. Thus you wind up with opinions like the one at this reddit thread:
I have also done websites in Hugo; the inflexibility of golang’s template processor was extremely limiting to the point that I would never use Hugo again.
To me, Hugo’s challenges are worth it because it is so much faster. But for the average content owner who writes a couple posts per month, and hasn’t yet (and possibly never will) hit the slowness of a 300+ post Jekyll site, they’ll likely find that they prefer the greater flexibility and simpler directory structure of Jekyll.
That said, I found Hugo’s tooling to be a lot better. It has HMR built right into it; when you edit a post and save your file, the browser page updates the resulting HTML content without doing a full refresh. The same holds true for any changes that occur to any referenced CSS or JS. It’s incredible. Also, creating a new post doesn’t require any custom tooling. I had this whole initialization script that I was using for Jekyll. Now it’s simply:
hugo new post/some-new-headline.md
With that, depending on how I’ve set up my archetype, I’ll have exactly the initial post data that I want. No custom initialization script necessary.
If you’re thinking of starting a new static project, go with Hugo. It’s blazing fast.
- Unless you handle your deploys abroad and then simply push the built HTML to GitHub Pages, as I do. ↩︎