Use Hugo to Automate your Website

After basing my software-oriented website on a hand-assembled set of pages, I recently decided it was time to introduce some automation. Suddenly there were too many pages that needed to be updated every time I released a new version of one of my apps. Additionally, I was writing more apps and utilities, so this process could only ever become more onerous if I continued with a manual set-up.

Years ago, the Electric Imp Dev Center was built using the Jekyll static site generator, though it has long since transitioned to Drupal. I pondered using Jekyll for my own site, but came across Hugo, a similar tool. Hugo seemed to have some buzz about it, and it’s written in and uses Go, which I was keen to try; Jekyll uses a mix of Liquid and Ruby. And, giving me a wee chuckle, my boss at Electric Imp is called Hugo, so I figured this was the one to try.

To cut a long story short, I remade my website in Hugo, and this post is about the tricks that I learned. If you’re planning a similar move, this post will hopefully save you some time and effort in getting to grips with Hugo.

I use a Mac, so installing Hugo is just a matter of running the brew package manager: brew install hugo. You will use your own OS’ equivalent.

Create a site using hugo new site {site_name} — add your own site name in place of the dummy text {site_name}.

This will create a folder named after your site; it contains a number of folders for your site’s components and the key ​config.yaml file which you use to configure your site. Here’s mine:

baseURL: "https://smittytone.github.io/"
languageCode: "en-us"
title: "smittytone’s software library"

# Make sure embedded HTML is not removed
markup:
  goldmark:
    renderer:
      unsafe: true
      autoHeadingIDType: "blackfriday"

# Set up the 'Related Software' block
related:
  threshold: 80
  includeNewer: false
  toLower: false
  indices:
    - name: "keywords"
      weight: 100
    - name: "content_type"
      weight: 50

# Do not use taxonomies, 
# ie. don't generate 'category' and 'tags' folders
disableKinds:
  - taxonomy
  - taxonomyTerm

The top section is self-explanatory.

You can add comments to help you indicate what does what. Hugo pages are formatted using Markdown, but I like to include raw HTML in situations where the built-in Markdown-to-HTML conversion doesn’t do what I need it it. Hence the markup: section’s presence and the setting of unsafe: to true — this is what allows you to include HTML in your markdown pages without it being removed.

Hugo has its own ‘related posts’ generator; the related: section controls that, matching pages by their keywords and content_type field values with suitable weightings.

Hugo allows you to organise content by type, called ‘taxonomies’. Examples might be blog posts, help pages, or whatever. I currently don’t use this feature, so I’ve turned them off by listing them in the disableKinds: section.

Hugo assembles pages by inserting the material from markdown files (stored in its content folder) into HTML templates (which live in layouts). You organise your site by creating a folder structure within content: the folder structure you establish defines your site’s sections. Each folder contains one or more page files, one of which can be the index page for the section that the folder represents. This file is takes the fixed name _index.md. You can name the other files whatever you like; their filenames will determine their URL path.

For example, the file content/apps/my_app.md will appear on your site at /apps/my_app/index.html. If you include the aforementioned content/apps/_index.md file, it will be used to create the page /apps/index.html. The URL component my_app is called the ‘slug’ It’s derived from the filename by default, but you can specify it on a per-page basis.

Your page files will contain two parts: the content itself and the ‘front matter’, which is a section at the head of the page in which you provide metadata about the page and its content. Hugo uses this front matter information to make sure the content is matched to the right template. Some files may have front matter and no content, or vice versa, or both.

You can use a variety formats for the front matter: YAML, JSON or TOML. I use YAML, and here’s the front matter from a typical page:

---

title: ASCII
description: "a tool to generate glyphs for 8x8 LED matrix displays"
slug: ascii
type: page
layout: app
content_type: macosapp
keywords:
  - imp
  - utility

index_icon: as.png
index_blurb: "Character glyph design tool for 8x8 matrix LED-based electronics projects."

head_icon_light: asw.png
head_icon_dark: asw.png
current_version: "1.2.1"
current_sha: "89be94a18c507f2cf7f8e6e627d8f2ed16dff6b236437585aa2af04ef4cc2698"
current_dmg: "ascii_1_2_1.dmg"
current_size: "1.1MB"
current_date: "2020-02-04"
github: "https://github.com/smittytone/ASCII"

menulinks:
  - "About ASCII"
  - "Release Notes"
  - "Source Code"
---

The pairs of --- mark out the front matter section. Some of the fields, such as slug:, title: and layout: are standard to Hugo, but you can add your own field, as I have done. For example, menulinks: list the items to be displayed at the top of the page and which link to sections within the page content. I mentioned the page slug earlier: here is where you set it so it’s used in place of the filename.

This page describes a software release, so the current_... fields provide release information that will be extracted and presented on the page. It’s a lot easier to update the front matter than dig down into HTML to find all the places where, say, the current version might be mentioned. Change it once and Hugo applies the change everywhere it’s needed.

Two key items of data are listed by the type: and layout: fields. Recall that Hugo adds a layout folder to hold templates. Here, you make a folder called, say, page and within that an HTML template called app.html. As you can see, page and app are the values of the type: and layout: fields. We’re telling Hugo which template to use and where to find it. The file app.html looks like this:

{{ partial "header.html" . }}
{{ $params := .Params }}
<body>
    <div class="container-fluid" id="ctop">
        {{ partial "navbar.html" . }}
        <div class="frame">
            <div class="row">
                <div class="col-1 col-lg-2"> </div>
                <div class="col-10 col-lg-8">
                    <h2 class="text-center">{{ .Title }}</h2>
                    <h3 class="text-center">A{{ strings.TrimPrefix "a" .Description }}</h3>
                    {{ partial "display_version.html" . }}
                    {{ if isset .Params "current_sha" }}
                        {{ partial "download_panel.html" . }}
                    {{ end }}
                    {{ .Content }}
                    {{ partial "related.html" . }}
                </div>
                <div class="col-1 col-lg-2"> </div>
            </div>
            {{ partial "rubrik.html" . }}
            {{ if eq .Slug "fightingfantasy" }}
                <p class="text-center"><small>Fighting Fantasy is copyright © 2016-{{ now.Format "06"}}, Steve Jackson and Ian Livingstone.</small></p>
            {{ end }}
        </div>
    </div>
    {{ partial "scripts_footer.html" . }}
</body>
</html>

The bulk is HTML. All the material within the curly bracket pairs, {{ and }}, is code that controls how Hugo modifies the page. The line at the top, for example, tells Hugo to load in the partial-page template header.html; the page has other such load instructions throughout the HTML. The idea is that you extract common elements from your pages, and pull each of these elements in as a partial template. Partial templates live within their own folder, partials, under layouts.

You can also see that we tell Hugo to drop in certain values. When it encounters {{ .Title }} and {{ .Content }}, Hugo swaps in the values from the current page: its title as set in the front matter and, in place of {{ .Content }}, all of the markdown file other than the front matter. Front matter fields are written all lower case, but remember to capitalise them in templates.

You remember that the front matter can contain your own fields? These are accessed here using .Params. For example, we use the isset function to see if .Params includes the field current_sha, ie. if its value ‘is set’. If it is, we load in a partial template that will make use of that (and other) front matter information. Further down, we use the eq function to compare the value of .Slug to a certain string; Hugo will add in the HTML paragraph below if the slug and string are equal.

We terminate the blocks with {{ end }} so that Hugo knows what to include and what not to. That material might contain more code: in this case {{ now.format "06"}}, which uses the now function to return the current date and time, which is passed straight into the format function to set how the date will appear: just the last two digits of the year.

Partial templates are only available for loading into page templates, but Hugo offers equivalent functionality for markdown files. Called ‘Shortcodes’, these too are small blocks of HTML that can be referenced in your markdown. For example, my pages contain two shortcode references: {{< end_of_section_link >}} and {{< source_code_link >}}. The former pulls in a simple block of HTML that provides a ‘click to go back to the top of the page link’ block, while the latter assembles the page’s Source Code section:

<h3 id="source-code-">Source Code</h3>
<p>You can view {{ .Page.Title | title }}’s source code <a class="link" href="{{ .Page.Params.github }}">at GitHub</a>.</p>

Once again, you can include code for Hugo to process during page assembly. Here we just pull in relevant information from the content file’s front matter.

Shortcodes are referenced by their filename minus the .html, so {{< source_code_link >}} points to source_code_link.html in the shortcodes folder, which is located in layouts. The main layouts folder also holds index.html, which is the template for your site’s homepage.

Other folders added by Hugo include:

  • archetypes You can create are markdown file templates for all of your content types here. They will be read when you use Hugo to make a new file using, say, hugo new apps/a_new_app.md, where apps.md is an template under archetypes and content/apps/a_new_app.md is the content file that will be created.
  • themes Downloaded themes are stored here. I didn’t use any because I have my own design, but you might choose to select one of the many available.
  • statics This folder contains all the content that Hugo is not expected to process: its sub-folders are just copied to the build folder, public. I have folders in static for my images, CSS files, scripts and downloadable files.

You can try out your site by running hugo server, which builds the site and serves it at http://localhost:1313 to a browser on your machine. It’s a live preview so you can tweak pages and see the result immediately.

When your site is ready to build, just run hugo. Your site will be assembled and the HTML pages and static files, if you have any, placed with a new folder called public from when you can transfer them to your server.

We’ve covered the key elements of Hugo; in a future post I’ll start to drill down into some of Hugo’s powerful codeable page-generation functionality.

More on Hugo…