2019/01: Since this publication, this blog has migrated from Hugo to Jekyll. Whilst this does not make the techniques described here invalid, it does mean that what you currently see does not match them.

One of my goals this holiday season was to relaunch my blog. My previous attempt failed because there was too much friction in actually blogging. It was an Iron-backed Rust web app using PostgreSQL and Diesel on a DigitalOcean host behind NGINX. It was my first time using most of these technologies for anything serious. I learnt a lot about them in the course of building that blog but the fifth blog post never saw the light of day. Deployment was too manual and enhancements were a time sync. Ultimately it became a burden.

Enter static site generators. I know I’m late to the party, but at least I now comprehend the benefit ;) Hugo is one such static site generator that seems fairly popular. And I can see why: it is so easy to get something up and running quickly. Here I’ll show you how to go from running that initial scaffolding command to hitting [insert your domain name] and seeing it fully deployed.

One thing of note is exactly how much Hugo balances convention (or opinion) against configuration. Hugo touts configuration as one of its strengths and this was an initial draw for me. I try to avoid heavily magic-laden frameworks like the plague. Sometimes that can go too far, when the desire to build everything from scratch is strong. So in an effort to find balance, I want to start with the absolute bare minimum and build up from there. We should not add anything that doesn’t have an explicit and understood purpose. Thus far, Hugo has accommodated that, so let’s begin.

Building the basics

Hugo already has a great quick start guide that we’ll follow to begin with.

First install Hugo with your package manager of choice.

# OS X buddies
$ brew install hugo

# Arch compatriots
$ sudo pacman --sync hugo

# Gentoo and friends
$ sudo layman --add go-overlay
$ sudo emerge --ask www-apps/hugo

Let’s create a skeleton site and add a theme. You can browse available themes here. I’ll be using the Minimo theme.

$ hugo new site wild-baguette
$ cd wild-baguette
$ git init
$ git submodule add https://github.com/MunifTanjim/minimo themes/minimo

This is all you need for a basic site without any content (yet). Let’s pause here and check out the folder structure.

$ tree -I minimo # ignore the theme directory
.
├── archetypes
│   └── default.md
├── config.toml
├── content
├── data
├── layouts
├── static
└── themes

Excluding the theme, there’s only two files! We’ll cover archtypes in a moment when we add some content, but for now let’s open up config.toml. You will likely need to add options specific to the theme you chose. Most importantly, actually set the theme with the theme key! You can find out relevant options for the Minimo theme on this commit I made for this site. Minimo has its own comprehensive configuration example, but the only required key is the recentPostsLength. Forgetting to add this key will give you an error upon starting the server.

Fire up the Hugo server and navigate to the URL displayed in the output. You should see something beautiful. If not, ensure your configuration is correct.

$ hugo server

Adding content and customising

What good are wild baguettes if you can’t find them? Being good citizens, we’ll share our knowledge on their habitats in our first blog post.

$ hugo new posts/where_to_find_them.md

The posts directory will be nested under contents in the project root. Fill this file with whatever pleases you. The important thing is the front matter. That’s the bit in the --- block at the top of the file. By default, Hugo will use the title for the post’s title (say what?) and the name of the file for its URL path. It’s important that title is set.

If you’re wondering what dictates that front matter, that’s the archetype! Hugo allows you to define your own archetypes to streamline adding the content you wish to provide. The default archetype suffices for a simple blog.

Nesting the new post under the posts directory will also nest it in the URL. With that in mind, it makes sense to have a posts page on the site. We need to create a new file for that.

$ echo '---\ntitle: Posts\n---' > content/posts/_index.md

Each section, or distinct part of our site, can contain a _index.md file that represents the section itself rather than its children, like posts do. The value we give to title here will be what’s shown on the section page and in the navigation bar. One more change: add sectionPagesMenu = "main" to config.toml. The value you pass here should be the name of your menu, which could be different for your chosen theme. For Minimo, it is main.

Start the server again and see our new navigation menu and post rendered. The -D flag here indicates we want to render draft posts in addition to regular ones.

$ hugo server -D

Let’s personalise the theme a bit. Minimo gives you a very easy way to override the CSS with the customCSS key in config.toml. You can read about a specific use case here, where the accent colour is changed.

Changing layout content is down to Hugo, so it should be independent of the theme. Say we want to add an additional line to the footer of our site. Simply add files to the layout directory. They must be the same name as the ones your theme uses. For Minimo, this meant adding a footer partial for the new content, as well as overriding the existing footer. You can read more about Hugo’s solution for customising your theme here.

Thus far, the only thing that tripped me up was Minimo requiring that one recentPostsLength key to have a value. Hugo itself has been completely transparent in what it’s doing. If you look at the folder structure of our project, you’ll see every file we’ve added has a known purpose. If we want to make adjustments to common configuration options, config.toml is a good place to start. If we want to override specific parts of our theme, we just provide the override in the layouts directory. The organisation of our site content will correspond to the folder structure inside the content directory. So far, it’s all very intuitive!

Deploying

I promised we’d deploy this so the whole world would know where to hunt for wild baguettes. Hugo has a good guide on Nanobox deployment. I hadn’t heard of Nanobox before so I did some digging. Essentially they provide a managed Docker container for you. They are not a cloud provider and rely on you having an account with a service such as AWS or DigitalOcean. Once you link the accounts, Nanobox will take care of deployments to the hosts of the cloud provider. Nanobox itself is completely free on the basic plan. Given one of the goals of this project was to streamline the process of actually blogging, this sounded like the perfect solution.

I’ll be using DigitalOcean here because I already had an account. Follow the Nanobox guide to generate a DigitalOcean key and give Nanobox read/write access to your account. Then launch the new app.

You’ll then need to install the Nanobox CLI locally. Nanobox needs two files for us to tell it how to run our Hugo project: boxfile.yml, which tells Nanobox what commands to run, and install.sh, which is called by boxfile.yml and actually installs Hugo inside the container. Add these files to your project, then tell Nanobox to deploy!

$ nanobox remote add <nanobox-app-name>
$ nanobox deploy

Visit your Nanobox portal and find a link that takes you to the deployed site! Too easy!

The next logical step is to set up a domain name for your site. I use NameCheap and have been very happy with their service. The prices are competitive and they provide a very intuitive dashboard for adding DNS records.

Next steps

There’s loads of places we could go from here. If you want your site to get even marginal traffic, definitely get a domain name for it. You’ll probably then want to look into adding TLS to your site. Even if you only serve static content, it’s always a good idea to enable it. After that, you can check out the depths of Hugo’s documentation. We’ve barely scratched the surface of what it’s capable of.

This will probably be the path that I take. I’m impressed with how easy the whole experience was, primarily due to the solid documentation and usability of Hugo. Hopefully you’ll see more content on here soon!