As of today, this site is no longer powered by the Ghost blogging engine. Before today, this blog was a web application with a database and a complex application server with a lot of moving parts. Now it is a bunch of
.html files. These files are generated by Hugo, a static site generator. Apart from a few unrelated CSS changes, you shouldn’t really notice a difference – although now you have to navigate to different pages to see different taglines, rather than just refreshing the front page.1 I’ve also tried as hard as possible to keep URL compatibility, so there shouldn’t be too many broken links here or elsewhere.
I previously wrote about wanting to make this move in my most recent retrospective post. In that post, I was mainly concerned about the speed and security gains from the change. What I didn’t anticipate was the increased level of control over the site’s content and layout the change would give me – and it’s a big increase, big enough to be a deciding factor in the battle between blogging CMSs and static site generators.
One small example of this increased control has to do with post images. Ghost lets you define an image for each post, which you can then put somewhere in the post listing on the front page (or tags page, or whatever). You’ll probably also want to put it at the top of the post’s actual page, and you can do that too.
But what if you want to put the post image at an arbitrary position within the post, but only for specific posts (such as this one)? In other words, you’re happy with the image going at the top of the page 95% of the time, but you’d like to put it somewhere else occasionally. With Ghost, there’s no easy way to do this: you have to forego placing images at the top of a page automatically and do your image placement manually for each post – and then you still have a situation where your posts will show up with what looks like a duplicated post image in some RSS readers. So that’s why I opted not to show post images on the actual page.
Hugo lets you be a little smarter about this. Because you can define arbitrary variables for posts and other pieces of content – and then access those arbitrary variables in your themes and layouts – you can have, say, a variable called
image_within, which is false by default, but if it’s true you can tell your theme “don’t print the post image on top, let the post handle it”.2
Now, one could also probably write a Ghost plugin to that specific thing, but my post image dilemma is only the tip of the Hugo iceberg.
Hugo’s power comes from its programmatic nature. Posts aren’t restricted to having, say, a publishing time, author and a list of tags – they can have any random metadata you want. And beyond that, site content isn’t restricted to date-stamped blogposts and static info pages: Hugo lets you define your own content types and taxonomies. A concrete example of this would be the stories on the Fiction page, which are neither posts nor pages, but their own type with their own templates.3
Hugo has also allowed me to automate the content of the Features, Fiction and Games pages rather than manually populating their many lists, which makes my inner programmer very happy.
In general, the move went a lot smoother and simpler than I imagined it would, and Hugo was flexible enough to allow me to keep 95% URL compatibility with the old site (I did have to write an nginx rule to get that up to 99% though). Very conveniently, someone had already written an automatic Ghost-to-Hugo post exporter, which I forked to align with my needs. I experienced a bit of pain due to differences in Markdown parsers (Blackfriday, Hugo’s parser, does not like
#Heading and will only render
# Heading, and of course the footnotes are totally different), but that was solved with a little script.4
So far as there was any real struggling, it was just around learning Hugo and figuring out how it wanted me to do the various things I wanted to do. The documentation, which I’ve linked to multiple times above, is very good, but not entirely current or exhaustive, likely due to the fast pace and activeness of development. A lot of very useful features and settings were only discoverable through threads on the issue tracker and forums – a dedicated Stack Exchange-style answer site (complete with years of past questions and answers) for Hugo would be incredibly useful.
- Flexible abstract content model: This allows me to do everything from having fiction pages as a discrete type of thing to generating the Games page from a template and a set of key–value data files.
- Powerful templating language: The Go template language (one of three templating languages Hugo supports) is almost a fully fledged programming language, and is made even more powerful by a plethora of Hugo-defined functions. It’s worlds away from something like Handlebars, and once you get used to the funky syntax, the logic you can implement largely makes up for Hugo’s inextensibility.
- The devs thought of everything: Every time I hit some annoyance that might have made me give up on using Hugo, a little bit of digging through Google and the documentation revealed a solution: shortcodes, aliases, the ability to pass dictionaries to partials, and custom configuration settings for everything from transforming
--into “–” to setting a custom footnote return link are just some examples.
So I guess the very telegraphed conclusion I’m coming to here is that I’m really happy with Hugo. It is a fast, robust and incredibly well-designed and fully featured static site generator that’s great for blogs but also holds far more potential. I recommend it without reservation to anyone technical enough to enjoy using it – basically, you should give it a try if you’re comfortable writing blogposts in a text editor and using a Git post-receive hook to do site updates.
Now that I’ve got the site move over with, I hope to be a bit more active with posting, so expect to see >1 post/month (how exciting!).
“This should drive up engagements!” said the marketing manager excitedly. ↩︎
You could also be cleverer still and have code in your template to search through post content for the URL of the post image to determine whether it needed to print it at the top, no booleans required. ↩︎