Why I have switched to Hugo
Previous versions of my website were built with various technologies. The first version used a custom content management system that I have implemented in Node.js using the Express framework. Then, realizing I don’t need a dynamic site, I moved on to Jekyll, which was fine, but eventually I grew weary of having to keep the Ruby environment alive and consistent on my Windows PC and my Mac as well, so I have decided to look for something else, something more reliable. I also admit I’m not the biggest fan of the Ruby language in general.
After some experiments I have settled down with Hugo, a static website generator, which is actively being developed by a group of dedicated programmers. Hugo is written in the Go language, and here is what I have to say about my experiences with it.
Hugo is unusual
It is a very common mistake among developers to assume they know a system just because they think they recognize the syntax or a certain pattern in it. Almost everyone who despises JavaScript, for example, has reached their conclusion because they thought that since it looks like Java and even was named after Java, it must be basically the same under the hood. But it’s not. (Though JavaScript is trying really hard to satisfy even these people by introducing features like the means to define block scope variables and the class syntax, among others.) Unless programmers learn that the this
keyword in JavaScript is not bound at instantiation but at execution, they will get bitten by the language, and of course the programmer’s wounded ego will happily accept JavaScript as scapegoat.
Naturally, I have had the same experience with Hugo. More than once, in fact. :) Let’s see a few of these!
The Templating Language
The documentation says that Hugo uses Go’s templating language (as default, but there are alternatives like Ace or Amber). This language, according to the documentation, is “extremely simple.” Well, that might be the case for some, but it’s certainly a most unusual one, and has a lot of quirks.
Let’s say you have a website about movies, and you want to generate pages where you list the movies’ crew. Some movies have multiple directors, and you want your page to be smart, and in these cases use the plural form of the word “Director.” This is how you could write this conditional check in the default templating language:
Director{{ if gt (len .Params.directors) 1 }}s{{ end }}
It reads rather differently than what one might be used to seeing in C-like languages like PHP or JavaScript that have dominated the web until recently.
Let’s break this example down! Everything between double curly braces is dynamic content. Our example starts with the literal word “Director” that is immediately followed by an if
—end
block which encloses a literal “s” character. It’s obvious that the if
block has to decide if it should allow that “s” to appear. This condition check should evaluate to true when the number of directors is more than one, and this has to be expressed for Go by using the gt
function which accepts two parameters and returns true if the first parameter is greater than the second. In this case the first parameter is an expression enclosed in parentheses, and the second parameter is the number 1 – function parameters are separated by white space instead of commas. The first parameter here is another function call, len
, which returns the length or size of the value it receives as its single parameter. In this parameter – .Params.directors
– the periods separate nested properties of an object, and the period at the beginning means that we want to use the Params
property of the current context, which is evidently a collection of entries that have directors
defined among its Params
property. I know it’s a mouthful but it just looks strange to eyes trained to read C-like code.
Formatting Dates
Another example would be the formatting of a date, which is also weird. At first it looks deceptively straightforward and you might assume that you get its logic. Soon you will discover that you need to consult the Go documentation or a helpful article for the details, because those month and day names and numbers have to be exactly like that, and they just represent the order (or the presence) of those date components like a month or the day which might or might not start with a zero, and so on. Therefore, you’ll encounter no easily recognizable, fugly placeholders like “dd-MM-yyyy” or “%Y-%m-%d %H:%M:%S” – instead you’ll get to write "Mon, Jan 2, 2006"
which has the following meaning: first I want to see the abbreviated name of the day followed by a comma, then the abbreviated name of the month, then the day of the month without a leading zero, followed by a comma, and finally the year. Nothing’s wrong with this, of course, it’s just so “human readable” that it’s easy to misinterpret these placeholders for an actual date.
Very efficient, yet very unfriendly
Even though I have more than 2 decades of experience in IT and spent more than half of it with web development, I haven’t had a streamlined experience with Hugo. Granted, it is very fast, but it is also very unfinished – its version at the time of this writing is only 0.27 –, and thus, it is very unfriendly. Whenever something goes wrong, the error messages are very verbose and cryptic, and there’s just no easy way to print out useful debugging information.
This is definitely not easy to remember: {{ printf “%#v” . }}
nor it’s easy to interpret its output in a randomly chosen context:
&hugolib.PageOutput{Page:(*hugolib.Page)(0xc420106000), paginator:(*hugolib.Pager)(nil), paginatorInit:sync.Once{m:sync.Mutex{state:0, sema:0x0}, done:0x0}, targetPathDescriptor:hugolib.targetPathDescriptor{PathSpec:(*helpers.PathSpec)(0xc420b3a540),[etc.]
Or, that at version 0.12 the developers have decided that a “pretty URL” must always have a trailing slash. This might be a reasonable decision from the perspective of technology, but it almost requires no effort to configure a web server to deal with its absence (Hugo itself understands an http request without trailing slash and willl automatically append it), because a trailing slash is just noise, not to mention taking away options is seldom a nice gesture, and that URLs without a trailing slash are rather common on the web. There are ways to have a clean URL like that in Hugo, but right now it’s not something you can easily achieve.
Naturally, these problems will go away as the project matures, but if you decide to give it a try, be prepared that Hugo can be an untamed beast sometimes.
The Ultimate Dealbreaker
Despite all of its peculiarities and rough edges, Hugo already has a huge advantage over its competitors like Jekyll: you download a single executable, and that’s it. Hugo does basically everything out of the box, without you having to install any programming languages, environments or utilities.
I cannot emphasize enough just how important and useful this is. When you decide you want to build a website you probably aren’t too happy if you also need to take care of a bunch of other things that may clash with your current setup so you immediately lose time without having made progress in your own tasks.
Being this accessible will profit Hugo, and without a doubt it’ll become friendlier, easier to configure and will offer sensible defaults, especially if it gains popularity which it deserves already. Check it out, you probably won’t regret it! :)
Ahem…
Actually, installing on Debian was promised to be as easy as sudo apt-get install hugo
.
Nope.
I had to download the .deb
installer (0.27.1) from the Hugo website, then execute sudo dpkg -i /path/to/deb/file
then sudo apt-get install -f
, which did the trick.