How I publish this blog

At the request of a couple of readers, here’s a post all about how the sausage is made. Some of this content can be found in the Hugo documentation and various online tutorials, but here it’s all linked together and contextualised. I still recommend reading other tutorials if you really want to set something like this up though, as I’m not going into exhaustive detail about everything here.

A table of contents is provided below for the convenience of those interested only in one or two sections of the process. The first covers using the Hugo static site generator, the second writing and formatting of individual posts, and the third Git hooks.

Layout and styling

This blog has been powered by the Hugo static site generator for about a year now. This gives me a lot of power and control over the site’s structure. Rather than being a pure blog-builder with dated posts and static pages, Hugo allows sites to built out of content types and taxonomies. The default content types are pages and posts and the default taxonomies categories and tags, but you can make more and name them what you want.

I use this in a couple of ways. The source code for this blog’s directory structure looks like this:

├── archetypes
├── content
│   ├── fiction
│   ├── page
│   └── post
├── data
│   └── games
├── layouts
│   ├── fiction
│   ├── partials
│   ├── section
│   ├── shortcodes
├── static
│   └── content
│       ├── audio
│       └── images
└── themes
    └── dyhugo
        ├── archetypes
        ├── layouts
        │   ├── _default
        │   ├── partials
        │   ├── post
        │   └── shortcodes
        └── static
            ├── css
	    └── fonts

archetypes contains templates for the three different content types: posts, pages and fiction. This feature exists as an aid for the hugo new {content-type}/{filename}.md CLI command, which creates new files according to the content type’s template.

The subdirectories in content house the meat of the blog. post is a repository of all the dated blogposts, page contains things like the about page, and fiction contains the stories in this site’s fiction section. Pretty self-explanatory, really. Each post/page/fiction piece is a plaintext markdown file with a TOML preamble specifying metadata such the publication date (for posts), an excerpt (for fiction) and the page title (for all content types). Here’s the head of content/post/how-i-publish-this-blog.md:

+++
author = "David Yates"
date = 2017-11-20T18:22:15+02:00
title = "How I publish this blog"
draft = false
tags = ["websites", "hugo", "git", "blogging", "markdown"]

+++

At the request of a couple of readers, here's a post all about how the sausage is made. Some of this content can be found in the [Hugo documentation](https://gohugo.io/documentation/) and various [online tutorials](https://www.digitalocean.com/community/tutorials/how-to-deploy-a-hugo-site-to-production-with-git-hooks-on-ubuntu-14-04), but here it's all linked together and contextualised. I still recommend reading other tutorials if you really want to set something like this up though, as I'm not going into exhaustive detail about everything here.

The data directory contains TOML files describing structured data used to generate the site – these can be thought of as content pieces with just the TOML preamble and no body text. At the moment, I only use this to generate the games page. Rather than go through the tedious process of formatting that page manually, I define each game as a set of structured data (name, description, release date, list of download links, etc…) which is fed into a standard layout template when the site is built.1 Thus this:

title = "Tron for Humans"
description = "A hotseat \"multiplayer snake\" made as a kind of aid for a bot-programming competition. This game is open source and MIT licensed."
release = "Developed: Q3 2011 | Released: 17 August 2014"
system = "Game Maker 8.1"
unsupported = false
screenshots = ["/content/images/2014/Aug/tronscreenshot99.png",
		"/content/images/2014/Aug/tronscreenshot101.png"]

[[downloads]]
title = "Google Drive (download w/source)"
url = "https://drive.google.com/#folders/0B9bAMoEXM_uOWFJWeE16anJ4X2c"

[[downloads]]
title = "64Digits (download w/source)"
url = "http://www.64digits.com/games/index.php?cmd=view_game&id=5820"

Becomes this:

The unique colours for each of the dev systems used are specified in another data file.

The unique colours for each of the dev systems used are specified in another data file.

This template and others are defined in the layout directory. This directory contains a number of HTML templates augmented by the powerful Go html/template language. While most of a Hugo site’s styling should be defined in its theme (see below), files placed in a site’s layout will override equivalent files in its theme. This allows you to tweak a chosen theme to your site’s needs without directly mucking its files up. This can very powerful if your theme makes good use of partials, allowing you to override the smallest possible units of layout. I’ve used it as a store for the more idiosyncratic parts of my sites, in order to keep the theme relatively clean and standard for the possible future in which I release it to the public.

The layout/shortcode directory deserves special mention: it defines Go html/template macros that can be used directly in content text. This is a great augmentation for the limited formatting powers of Markdown and, unlike pure HTML, can access Hugo variables. I have shortcodes for the table of contents used in this post, for image captions, and for the yellow pull-out boxes used in some posts.

I also use shortcodes to generate the archive page and the various lists of posts on the features and misc pages. This saves a lot of irritating manual editing – I can say “list all posts with the tag ’notable’” rather than manually maintaining a list. It also allows me to radically change the way said lists are displayed very easily – I recently took advantage of that to change all the boring bullet lists into pretty picture boxes.

static contains assets such as images and pregenerated HTML pages such as these ones which are not to be mucked around with by Hugo, but merely included with the compiled site. static/content/images is not static/images because of my desire to preserve as much URL compatibility with the old Ghost version of this blog as possible and because I haven’t gotten around to using an nginx rewrite rule instead.2

themes contains the site’s theme, bearing the inspired name dyhugo, and itself containing archetype, layout and static folders, which do the same thing as the ones described above, but in a more generic way – archetypes are provided for posts and pages, Go html/template layouts for most of the site are defined, and static contains webfonts and CSS. To further enforce the separation between the theme and the site, dyhugo is in a separate Git repository, defined as a Git submodule of the site’s repo.

Hugo’s layout engine is flexible and the Go/html templating language is unexpectedly powerful, so a lot can be done in templates that would require plugins for other blogging engines, static and otherwise. For example, the template used for displaying posts on the frontpage performs a number of text substitutions on each post’s excerpt to remove things like captions and headings in order to make it reasonably coherent.

Writing

Whenever I get an idea for a new post, I run this command in the blog’s source directory:

hugo new post/new-post-idea.md

This creates a copy of the post template at archetype/post in the content/post directory which looks like this:

+++
author = "David Yates"
date = <current datetime>
title = "New Post Idea"
draft = true
tags = ["x", "y"]
# image = "/content/images/201X/XX/" # image is commented out initially

+++

I can also add other attributes I’ve defined in my theme to the frontmatter, such as image_within=true, which will prevent the theme from placing the post’s image at the top in posts where I’ve purposely placed it elsewhere in the body.

I then start up a local webserver hosting the blog with the command:

hugo serve --buildDrafts

Hugo builds the blog (including all draft posts), hosts it locally, and watches for changes to files, rebuilding the site when it detects any. Hugo also injects JavaScript into the blog’s pages to refresh the browser viewing it. This gives me a live preview of what my post will look like while I write it in markdown using Vim.

Previously seen in this post.

Previously seen in this post.

Markdown gives me a good range of formatting options, including headings, bold, italic, code, strikethrough, and even footnotes (which I overuse) and tables (which I have yet to use). This is augmented by the custom shortcodes mentioned in the previous section, which I use for tables of contents, image captions and yellow pull-out boxes. Hugo also provides a number of builtin shortcodes for things like linking to other posts and embedding Github gists. And of course pure HTML is always an option.

I try to be fairly rigorous with the research and preparation I do for my posts and to go over the language and structure of each one as much as possible prior to publishing, but this varies from post to post. Ultimately this is a personal blog I write largely for my own enjoyment, so the language is fairly loose and doesn’t see the same amount of polish prior to presentation as I put into more serious writings. I also lean pretty heavily on footnotes and pull-out boxes to save me both the effort of lots of restructuring and the judgement of when to cut tangential rambling. This is my writing at its rawest.

I also edit old posts for spelling and clarity sometimes, but I endeavour not to change the overall thrust or opinion of the post. In cases where I no longer agree with a post, I’ll sometimes include a dated edit near the bottom.

I try to balance the content of the blog between technical posts like this one and non-technical posts like my various reviews, but again, it’s mostly about writing what I want to write and exploring stuff that interests me, which shifts over time.

Deploying

After I’m done working on a post and want to publish it, I alter the datetime in its frontmatter and set draft=false. I then build a local version of the site without drafts to make sure everything is displaying right. Once I’m happy with that, I run git push prod to send the newest version of the blog to my server, where it is rebuilt and deployed.

If you’ve used Github/Bitbucket/Gitlab/etc before, you’ll know about git push – it’s the command you use to send new local commits to the great big server in the cloud. What you’re actually doing there is syncing your local repository with a repository on Gitbucketlab’s server, and that’s something you can do just as well with a repository on a cloud server of your own. Just create a bare clone of your local repository and scp it up to your server.

git clone --bare ~/blog /tmp/blog.git
scp -r /tmp/blog.git username@ip:

Then add the newly clouded repo as a remote for your local one:

git remote add prod username@ip:blog.git

Now you can run git push prod and it’ll push all your new commits to the repo on your server.

Git also provides hooks, used to specify scripts to be run on certain events. For example, you can have a script that runs your tests and checks they’re all passing just before a commit or a script that rebuilds your Hugo website after it receives new commits via a push.

The script in question ($REPOGITDIR/hooks/post-receive, largely copied from this tutorial):

#!/bin/bash

# ... environment variable definitions ...

set -e

# Clear out the working directory and make a backup of current site in case something goes horribly wrong
rm -rf $WORKING_DIRECTORY
rsync -aqz $PUBLIC_WWW/ $BACKUP_WWW
trap "echo 'Build failed. Reverting to backup.'; rsync -aqz --del $BACKUP_WWW/ $PUBLIC_WWW; rm -rf $WORKING_DIRECTORY" EXIT

# Create new working directory
git clone $GIT_REPO $WORKING_DIRECTORY
cd $WORKING_DIRECTORY
unset GIT_DIR

# Pull down theme repo
git submodule update --init --recursive

# Delete current site and build it anew
rm -rf $PUBLIC_WWW/*
/usr/local/bin/hugo -s $WORKING_DIRECTORY -d $PUBLIC_WWW -b "https://${MY_DOMAIN}"
mv $PUBLIC_WWW/rss.xml $PUBLIC_WWW/rss # fudge in the sacred name of URL backwards compatibility 
rm -rf $WORKING_DIRECTORY
trap - EXIT

All this ultimately does is create a bunch of HTML files in a directory; I still need an actual webserver to host my site’s content. For this I use nginx. My configuration does the following:

  • Hosts all files in $PUBLIC_WWW.
  • Rewrites a couple of requests to preserve URL backwards compatibility.
  • Defines SSL stuff: I use a Let’s Encrypt certificate, renewed with acme-tiny, and implement most of the cipherli.st configs. But you shouldn’t actually see that because I put the Cloudflare CDN (free tier) in front of it.
  • Redirects attempts to access the site over HTTP or with a leading www.3
  • Serves Hugo’s 404.html as an actual 404 page.
error_page 404 /404.html;
location = /404.html {
	root $public_www;
	internal;
}

Now, having a whole server to host a bunch of static pages may seem like overkill, especially in the Year of Our Lord Two Thousand and Seventeen. With a bit of finagling, I could replicate this exact setup with Git[hub|lab] Pages or an S3 storage bucket. So why do I spurn the blessed gifts of our cloudy benefactors?

Well, mostly because I like having a server. This site has no server-side logic at present, but I like having the option of adding some in the future should I so choose. Commenting, for example.

And that’s it. Further questions, comments or suggestions can be mailed to or tweeted at me.


  1. If I wanted to give each game its own page, I could achieve the same effect by making game another content type. ↩︎

  2. Fun fact: images is subdivided into directories based on the year and month of the image’s uploading. Astute readers will be able to figure out how long I sat on posts before publishing them from the length of time between an image’s date and that of the post containing it. Also the 2014 folders switch from “Apr”, “May”, “Jun” and “Sep” to “11” and “12” because I guess someone at Ghost changed their mind about date formats in early alpha. ↩︎

  3. No point in having a sweet domain hack like davidyat.es if I allow people to precede it with the decidedly crusty and unaesthetic www, now is there? ↩︎


similar posts
webmentions(?)