Build and host a blog using Hugo and Netlify

Last modified on 2017-11-12

A static site generator takes a collection of plaintext files written in a markup language (Markdown, reStructuredText), templates (layout), images, CSS, and compiles the contents into HTML output suitable for a blog. This is how I use Hugo to build this blog and publish it courtesy of free hosting by Netlify.

Let’s go!

I like the idea of creating a static website. Some of the advantages:

The traditional disadvantage of a static site has been the flipside of a lack of dynamic elements - no database for things like user comments - but Javascript and third-party services like Disqus are alternatives to work around static limitations.

I originally started my blog using hand-crafted HTML, moved to a static site generator called Pelican, and have now converted to Hugo. Pelican was OK but I was starting to find my blog a bit cantankerous to work with and considered overhauling the templates and clean up my patched together theme. I also wanted to setup SSL certification. I heard good things about Hugo and wanted to see how other static site generators did the job. After a little experimentation I decided to discard my plans of tinkering with the existing setup and move straight to Hugo.

Things I like about Hugo:

So the plan is to build this blog using Hugo, work out the dings, upload the finished site to GitHub, and use Netlify for SSL (https) hosting.

0. Install Hugo and create new site

Full details for getting started.

Linux: Download tarball for the latest release, unpack, and I place hugo in ~/bin. Test by running …

hugo version
  Hugo Static Site Generator v0.30.2 linux/amd64 BuildDate: 2017-10-19T07:34:27-04:00

Create a new directory for this site - circuidipity - with the default layout of a Hugo site …

cd ~/www
hugo new site circuidipity
  Congratulations! Your new Hugo site is created in /home/dwa/www/circuidipity.

[Requires git]: Add a theme using git submodule (when later deploying the site using Netlify its the only method supported). Example: I choose the Minimo theme …

cd circuidipity
git init
git submodule add themes/minimo
git submodule init
git submodule update
mv config.toml config.toml.bak && cp themes/minimo/exampleSite/config.toml .

Link: Git Tools - Submodules - “Submodules allow you to keep a Git repository as a subdirectory of another Git repository. This lets you clone another repository into your project and keep your commits separate.”

Add content …

hugo new posts/

Start the Hugo test server with drafts enabled …

hugo server -D

Navigate to the new site at https://localhost:1313/

1. Work out the dings

1.1 Convert existing content

I am not starting a new site from scratch, but rather converting an existing site that uses rst markup to Markdown (Blackfriday is Hugo’s built-in Markdown rendering engine). This was the biggest task moving from Pelican to Hugo. Using shell tools to write front matter to files and nvim’s global search/replace function are a big help but I still have to do some manual tweaking. I have less than a hundred files to convert so no big deal, but for a site of hundreds or thousands of pieces of content more serious script fu is required. Maybe enlist the help of Pandoc?

1.2 Redirects

Redirects will be configured later in a dedicated file for this purpose when setting up deployment for hosting. I originally was using aliases in the files to redirect old URLs but this generates problems on Netlify. Avoid aliases!

1.3 404 not found

Custom 404 page: Copy theme’s version to layouts/404.html and customize. The built-in server will not fallback to the page. Test by going directly to localhost:1313/404.html.

1.4 Templates and custom CSS

The other thing I picked up through trial-and-(many)-error is Hugo’s use of the Go templating language for laying out site content. Basically, the chosen theme includes the templates and css under themes/my-chosen-theme/layouts/* for its idea of how the site should look and flow. If I want to change something, I duplicate the path under my own layouts directory and copy over and modify the item of interest. This allows me to track the improvements of the theme author without the danger of overwriting my changes by mistake. When Hugo builds the site its lookup order assigns priority to the contents in layouts. Some examples are below.

Link: Minimo CSS customization made easy using custom.css

1.5 Fonts

Custom fonts: I use the Yanone Kaffeesatz (headings) and Ubuntu (body) fonts.

Add the Google Fonts API to the site <head>. I copy themes/minimo/layouts/partials/head/includes.html to my layouts/partials/head/includes.html and modify …

<link rel='stylesheet' href=''>
<link rel='stylesheet' href=',400i,700&subset=latin'>

… and set headings to Yanone Kaffeesatz in my static/css/custom.css

.title,h1,h2,h3,h4{font-family:Yanone Kaffeesatz,sans-serif;font-weight:700;line-height:1.2;margin:0 0 .625em}

1.6 Tags and Tag Cloud

Lowercase keywords: Minimo theme defaults to capitalizing tag names, but I want the tags displayed in all lowercase. Change the behaviour by copying taxonomy-list.html and term_cloud.html to layouts/partials/entry/taxonomy-list.html and layouts/partials/extras/term_cloud.html respectively, and modify in each file …

{{- .Title -}}
    {{- else -}}
{{- $term -}}

… to …

{{- .Title | lower -}}
    {{- else -}}
{{- $term | lower -}}

Alphabetical tag cloud: Theme default is to shuffle the order of tags displayed. I want the tags in alphabetical order. After struggling a bit to get this working, I opened an issue with the theme creator, and within an hour(!) he replied with new code that added a noTermCloudShuffle option for insertion in my config.toml file. Good stuff!

1.7 RSS

Custom RSS feed: Copy the default rss template to layouts/_default/rss.xml and customize. To display the entire post in the feed - not just a summary (default) - change .Summary to .Content

<description>{{ .Summary | html }}</description>

… to …

<description>{{ .Content | html }}</description>

To exclude the documents in page/ from the RSS feed (I only want to include documents in posts/), change …

{{ range .Data.Pages }}

… to …

{{ range ( where .Data.Pages "Section" "ne" "page" ) }}

I modify the site configuration config.toml to:

mediatype = "application/rss"
baseName = "feed"

rssLimit = 10

name = "Daniel Wayne Armstrong"
email = ""

Links: Full content Hugo feeds and Change the rss url

2. Deploy

2.1 GitHub

I already have an existing repository for circuidipity on GitHub (otherwise, create a new repository for Hugo content). Previously I used GitHub Pages to host this blog, but currently GP does not support SSL on custom domains.

Git does not track empty directories. Netlify requires all the directories generated in a default Hugo layout to be present in order to successfully build the site for hosting. On my blog all the directories contain content and are tracked by git except data/.

So I add an empty file to data/ , track it with git, and push my changes to GitHub …

touch data/.addme
git add data
git commit -m 'first commit'
git push -u origin master

2.2 Netlify

Netlify works by building the contents of my Hugo git repository into a static website and copying that output to its servers deployed worldwide for hosting.

I login to Netlify with my GitHub credentials, create a new free account, and elect to create a New site from Git with continuous deployment. That is, Netlify will watch my repository for any changes, which trigger an auto-rebuilding and deployment of my blog. “[Git] push and publish”. Good stuff!

Hugo Version

Its possible a build might fail due to a project using new Hugo features not available in the older version used by Netlify to compile the site. Avoid this by setting HUGO_VERSION as a build environment variable in the Netlify console. Example: I set HUGO_VERSION 0.30.2 and Netlify at build time will download and use this version to do its thing.

Build settings

First-time build completes. Netlify generates a subdomain address at

In Settings->General I set my own subdomain name to

Link: Hosting on Netlify

2.3 Custom domain

Using a custom domain on Netlify …

You’ll need to point the DNS records (usually both “www” AND the bare domain) for your domain at our servers.

First, assign the domain in the Netlify dashboard. Next, configure DNS either through the domain registrar or turn over DNS management to Netlify. Registrars differ in the details of how to do this, but in my blog’s example - where is the primary domain and redirects to www domain (Netlify automatically will do a 301 redirect to www) - I need to add a CNAME record for www

www     CNAME

… plus an A record for pointing at the Netlify load balancer’s IP

@   A

2.4 Redirects

Top level domain auto-redirects to www, but I ran into the problem where the the section pages of the site would load, but selecting any of my indivdual posts would result in an endless loop of trying to load the page without success.

[ Fix! ] The problem turned out to be the aliases in the front matter of the affected files. I removed the aliases and created a _redirects file as per the Netlify Redirects HOWTO to hold all my 301 redirects.

2.5 SSL

HTTPS on custom domains courtesy of Let’s Encrypt.

Before enabling SSL, I confirm DNS is properly configured by examining the HTTP response headers for my Netlify hosted sites …

curl -s -v 2>&1 |grep Server
curl -s -v 2>&1 |grep Server

Response should be …

Server: Netlify

In the Netlify dashboard, go to the SSL screen and click Let’s Encrypt Certificate. Provisioning the SSL service should take less than a minute. Try out the new https addresses.

Set the Forcing SSL option, which will redirect any traffic to the old https to the new https. Ensure that DNS is working properly before selecting …

Warning: once you force HTTPS any browser that visits your site will ONLY connect to the site again via SSL for the next year. So make sure to verify in a browser that SSL is working properly on your site, under all names, before enabling this!
Most importantly, you’ll need to configure the DNS for the custom domain before you can provision a Netlify certificate. To provision the certificate, Netlify needs to go through a domain validation process on your behalf, and this step cannot be completed unless the DNS records for your custom domain is already pointing at our servers.

2.6 Onward

If you are reading this, it means its working. 👽

Happy hacking!