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.
This post is not meant as an indictment of Ghost. As a minimal blogging CMS, Ghost is fantastic, and it served me well for over two years of writing this blog. Were it to implement all the features I’m going to praise Hugo for in this post, I fear it would become a WordPress-sized hulking brute of a thing with lots of awful GUIs (though that may be an inevitability in any case).
As a dynamic blogging platform matures, the probability of feature-parity with Wordpress approaches 1.— David Yates (@davidyat_es) June 13, 2016
If you just want a simple, slick blog with posts and pages and don’t want to micromanage every little thing about it, Ghost is a fine choice. It’s just a choice that I’ve outgrown.
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
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.
That kind of thing is really appealing to programmers, at least in the abstract. Especially the part where you only use the plugins you need, to create a bloat-free minimalist site totally tailored to your needs.
Then, of course, you start thinking about what a minimal but still functional blog needs. The “simplest blog” example on the Metalsmith site uses two plugins: a Markdown parser for formatting posts and a layout engine for placing them nicely in an HTML template. Which, sure, is technically functional, but would be a pain for both the author and the reader. To get something actually usable you’d have to add:
- An RSS feed, so readers could subscribe instead of checking the homepage periodically like animals.
- Drafts, so the author could work on posts over more than one sitting before publishing them.
- Tags, to organise posts in a vaguely sensible way.
- A post scheduler sure would be nice.
- Oh, there are plugins for post series and in-post tables of content!
- Gotta have excerpts for post lists and some pagination!
- Speaking of pagination, next and previous post links are a must.
- Code highlighting!
- Automatic image minification!
- Smarter headings!
- Google Analytics and Github gists!
And before you know it you’ve got over 100MB sitting in
node_modules and your minimalistic blogging website is not so minimalistic at all, but you’re still going to need to write your own extensions for X and Y behaviour you need, and oh this extension doesn’t play nicely with this other one, and it looks like you mixed up the order here, and wouldn’t it be nice if this extension had some extra options, and why is my site taking this long to build?!
So Metalsmith was right out.
- 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. ↩
- Which, now that I look at it again, should really have used a better regex for headings. Let me know if you find any weird formatting it may have caused. ↩