Creating a Static Site with Hugo

Posted on
21 min read | 4399 words

Last post I explained that i’m moving this blog from Wordpress to the “JAMstack” by using a set of technologies to build and deploy a static site. The first of those is a Static Site Generator and in this post i’ll describe how I got things up and running.

If you want to jump straight to the implementation part, you can use these shortcuts:

Installing Hugo and Creating a Site
Adding a Theme
Overriding Theme Styles
Creating Content
Adding Different Content Types
Creating Archetypes
Creating Templates
Updating the Navigation Menu
Creating Categories and Tags
Viewing the site Locally
Deploying the site to Netlify

What is a Static Site Generator?

Static Site Generators have exploded in popularity in the last few years but the concept is not a new one. The very first websites were simple static HTML files after all. The difference with modern static site generators is they allow us to leverage powerful technologies to have dynamic functionality with the speed and security of static sites.

The Smashing Magazine article Modern Static Website Generators are the Next Big Thing (2015) outlines three key features common to all Static Site Generators:

Templating

Breaking a site down into reusable layouts through templates. This is similar to the kind of php templating Wordpress has provided for years, now done on the frontend. There is a focus in general on modular, reusable components in front end development, and templates are a logical choice.

Markdown

Rather than writing content in HTML, markup languages like markdown allow us to store it as plain text with human-readable shorthands for formatting. It’s ultimately the latest evolution of the separation of content and design.

Metadata

This is the additional information relative to our content. The data about data. In Wordpress terms, things like the author and date of a post are meta data. But we don’t just create blog posts any more. Many portfolio sites now include projects and case studies, for example. Things like client, role, duration, and technology can all be relevant metadata.

This is what Front Matter is for. It allows us to outline meta data inside our documents and templates. It’s typically written in YAML, making it a nice human-readable format.

Back to Top

Choosing a Static Site Generator

I actually tried Eleventy first, an extremely popular Static Site Generator that i’ve heard nothing but good things about. However, although I was able to create a blog in a couple of clicks using Eleventy, I found customising it to my needs confusing and frustrating. I put this down to my lack of understanding of how everything fits together rather than an issue with Eleventy itself.

I would like to try Eleventy in future. Part of feeling my way around involved using Andy Bells’ Hylia starter kit for Eleventy. It has really nice features including a styleguide and design tokens so would recommend.

I ended up using Hugo as my Static Site Generator instead. I found the documentation much more beginner-friendly. It walks you through from start to finish, building up the knowledge required. Something just ‘clicked’ easier with the way concepts were described in Hugo for me.

Admittedly, part of the attraction of Eleventy for me was that templates can be written in (amongst many options) JavaScript, whereas in Hugo they are written in Go. I wanted to avoid having to learn another language if I could help it, but I think it’s a minor trade off since I don’t need to create that many templates. Either way, my content itself will be written in Markdown which i’m already familiar with.

Although not a concern for my small site, Hugo is also proven to be blazingly fast even with thousands of blog posts. It’s nice to know that it operates well at scale.

In hindsight, I realise also that my mental model from Wordpress was tripping me up when it came to the distinction between posts and pages. As I now understand it, there isn’t one. There are only collections of content and these can have any type you determine. I think this is covered in the Eleventy documentation, but I didn’t actually understand this until I discovered Hugo.

Back to Top

Installing Hugo and Creating a Site

I followed the Quick Start article, installing the Hugo binary for macOS using homebrew from my Terminal:

brew install hugo

Then running the following where ‘personal-blog’ is the name of the folder that was created:

hugo new site personal-blog

The resulting files and folders were as follows:

  • archetypes
  • config.toml
  • content
  • data
  • layouts
  • resources
  • static
  • themes

I found a really good explanation of the key files and folders in Hugo’s Directory Structure Explained by Jake Wiesler, but essentially:

  • Site settings are defined in config.toml
  • Content lives in the content folder
  • Templates are defined in layouts
  • Static assets like CSS, JS, fonts and images live in the static folder
  • Default parameters for content like ‘title’, ‘date’ etc are defined in Archetypes
  • Themes live in the themes folder

Back to Top

Adding a theme

Hugo doesn’t come with a theme so next the guide advises installing one. I initially used Ananke as suggested but am now using the Pickles theme. This was as simple as cloning a theme from GitHub into a repositiory in the themes folder using the following commands:

cd personal-blog
git init
git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke

Here I am:

  • Changing into the personal-blog folder that was created
  • Creating a new git repository (in this case, making the existing project a git repository)
  • Cloning the Ananke theme repository into a git repository within my main repository, allowing me to later pull any updates to the theme (see git submodules)

I then added a line to the config.toml file to point to this theme. This can be manually edited with a text editor or you can enter the following in the Terminal:

echo 'theme = "ananke"' >> config.toml

The config.toml file now includes a ‘theme’ parameter:

title = "My new Hugo website"
baseURL = "https://example.com"
languageCode = "en-us"
theme = "ananke"

Just a heads up

It’s worth pointing out that if you follow the next set of Hugo quickstart instructions to create your first post and start a local web server to view your site, it won’t look as expected. For one thing the header image is missing.

Although the guide doesn’t mention it, it’s necessary to follow some of the instructions from the Ananke Theme page to configure the theme. You need to set things like the path where the header image is found, for example. An example is included in the Ananke exampleSite folder. You can simply copy some or all of the settings from the exampleSite config.toml file into your own site’s config.toml file.

One thing to watch out for though is that theme name is incorrectly shown as “gohugo-theme-ananke” in the example site config.toml. It needs to match the name of the theme folder which is actually “ananke”, so if you copy the exampleSite config.toml, you need to change this line.

You can also remove themesDir = "../.." line since this was used by the exampleSite to point to the theme folder, two levels above where the exampleSite config.toml is located, which is not the case for my site.

Back to Top

Overriding Theme Styles

Including a CSS file in the static folder and providing a link to the stylesheet in the header layout file allows the CSS defined in the theme to be overwritten. However, themes like Ananke make this easier by providing a custom_css parameter in config.toml that you can instead set to your custom CSS file. This Hugo forum post explains how to set this up for themes that do not have this.

For example, I created a file called custom.css in a static/css subfolder:

cd static
mkdir css
touch custom.css

and linked to this in config.toml:

[params]
custom_css = ["css/custom.css"]

This is where i’ve made all my style changes for things like post metadata, thumbnail images and forms.

Back to Top

Creating Content

New content (blog posts, pages etc) are created as Markdown files in the content folder. In Hugo, subfolders can be used to create sections of content. This is most useful to seperate, for example, posts and projects, since these first level folders also determine the content type.

For example, a content Markdown file can be created from the Terminal using the following:

hugo new posts/my-first-post.md

This asks hugo to create a Markdown file called ‘my-first-post’ in a posts subfolder (it will be created if it doesn’t already exist). These files are created in the content folder by default.

You can alternatively, create a markdown file in any text editor and manually place it in a ‘posts’ subfolder in the Hugo content folder. The benefit of using the terminal is Hugo will include any “Front Matter” that has been defined in the relevant Archetype file (more on that below). The file created by Hugo has this content, for example:

---
title: "My First Post"
date: 2019-03-26T08:47:11+01:00
draft: true
---

The content between the triple dashes — at the top of the file is the YAML Front Matter. This is where you set variables like the post title and date. These variables can then be used to display content using a different template or pulled through by the theme for use in the post. Hugo comes with some that are predefined like title, type and layout, but you can also create your own. For example, I use a series of variables on my Project Ghibli posts that are used for the Film metadata at the top of each post.

Below the second set of triple dashes is where you write your Markdown, the markup language used to format your documents. The syntax is very simple to learn - see Markdown Guide for a reference. I already use it at work with our wiki platform Confluence and our issue tracker YouTrack. It’s such a pleasure to work with. If you’ve ever used the delightful notetaking application Bear on macOS/iOS, you’ve already used Markdown.

Adding Different Content Types

As I mentioned, it wasn’t until I started exploring Hugo that I realised that there was no separation between pages and posts. As far as the Static Site Generator is concerned, they are both just pieces of content.

Hugo is clever enough to determine the content’s type from the type set in the Front Matter of the Markdown file or, if not specified, from the first directory in the file’s path. My blog posts for example, automatically inherit a type of ‘posts’ from the containing directory (‘posts’) since I didn’t add a type to the Front Matter.

However, my About page is at the top of the content folder so there isn’t an appropriate directory to inherit from. I can instead specify the type in the Front Matter of the content file. This takes precedence over the directory so the page could be anywhere in the content folder and the type would still apply. In her article Migrating from Jekyll+Github Pages to Hugo+Netlify, for example, Sara Soueidan used ‘static’ as the type for fixed pages like the About page.

That said, in Hugo, content is type ‘page’ by default so i’ve left this alone for now. Instead, I specified a review type for my Project Ghibli articles:

---
title: "Gauche the Cellist"
date: 2020-04-25T22:05:54+01:00
draft: true
type: review
---

The keen eyed may spot the use of +++ rather than — in Sara’s article:

+++
type = "blog"
description = "..."
title = "..."
date = ...
+++

The difference is simply that +++ is TOML format while — is YAML format. Hugo supports both (and also JSON) and I went with YAML since this is what posts used by default. The configuration file uses TOML though (config.toml) so I may end up making this a yaml file later too because I prefer the colon format over equal signs.

A word on Type vs Section

I thought I had this straight early on but since the type can be inferred from the Section, I found myself confused.

From Wordpress to Hugo - A Mindset Transition by Régis Philibert does a great job of clearing some things up:

Type

In a framework like WordPress, every entry is a post of different types. A post is a post of type post, a page is a post of type page and a recipe is a post of custom post type recipe (or whatever you chose to name it).

In Hugo, every entry or content file is a regular page of a different type. And because there is no built-in type, every type is your own custom type.

-- Régis Philibert

Section

A Section is defined from the organisation of content and not set by the user. By default a post will have the same section and type:

“By default, everything created within a section will use the content type that matches the root section name. For example, Hugo will assume that posts/post-1.md has a posts content type.”

-- Official Hugo Documentation

The type can be overwritten by the Front Matter. This may be on a content by content basis or automatically applied using Archetypes (more on that next)

Back to Top

Creating Archetypes

While it’s fine to add parameters to the Front Matter of files during editing, Archetypes allow you to save time by predefining the parameters to add to certain types of files when they are created using hugo new. These parameters can then be used in the content templates (layouts).

For example, I made a review Archetype (review.md) with parameters that would be added to my Project Ghibli posts:

---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
type: "review"

featured_image: # e.g. "images/featured.png"

# taxonomies
categories:
- Project Ghibli
tags:
- anime
- studio ghibli

# Project Ghibli metadata
release: # e.g. "23rd January 1982"
genre: # e.g. "science fiction"
directors: # e.g. ["Isao Takahata"]
producers: # e.g. ["Kôichi Murata"]
screenwriters: # e.g. ["Isao Takahata"]
alternative_titles: # e.g. ["Gōshu the Cellist"]
---

As far as I understand it, including the content type is unnecessary since this will already be based on the Archetype filename, however I found it useful to include it where I wanted to override this (for example, initially I had both a ghibli-review and a film-review Archetype and I set the type for each to review).

Any content files created using the hugo new ghibli-review command such as:

hugo new ghibli-review/gauche-the-cellist.md`

would then be created in the content/ghibli-review directory (it will be created if it does not exist) and would have the review type:

---
title: "Gauche the Cellist"
date: 2020-04-25T22:05:54+01:00
draft: true
type: review

# Project Ghibli metadata
...
---

I later decided just to have a single review Archetype and type, and to only use certain fields in the template if they had values.

The hugo new command uses the content section to find the most suitable archetype template in your project. If the project does not contain any Archetype files, it will also look in the theme.

Back to Top

Creating Templates

In Hugo you can define templates for the way that types of content will be displayed. In Hugo, every piece of content is a Page and there are three key types: home for the homepage, single for single pages like a blog post or the about page, and list for list pages like category and tag pages. See Content Organisation by Mike Dane.

Templates are defined in the layouts folder. To create the template for my Project Ghibli posts, I copied the single.html file from the theme into a review folder in layouts. This folder was named review to match the type defined in the Archetypes.

I then adjusted this to display my custom parameters for directors etc. The Go syntax was fairly easy to understand: Front Matter parameters can be accessed via the .Params variable, while .Directors is the custom parameter I created. I made these fields arrays so that I can provide multiple names and use range to iterate over their values. Here i’m looping through the Directors array, with the . referring to the current context (in this case, the current item in the array):

<ul>
  {{ range .Params.Directors }}
  <li>{{ . }}</li>
  {{ end }}
</ul>

Here is the above list in the context of a single.html template:

{{ define "header" }}{{ partial "page-header.html" . }}{{ end }}
{{ define "main" }}
  <div class="flex-l mt2 mw8 center">
    <article class="center cf pv5 ph3 ph4-ns mw7">
      <header>
        <p class="f6 b helvetica tracked">
          {{ humanize .Section | upper }}
        </p>
        <h1 class="f1">
          {{ .Title }}
        </h1>
      </header>
      <div class="nested-copy-line-height lh-copy f4 nested-links nested-img mid-gray">
        <ul>
          {{ range .Params.Directors }}
          <li>{{ . }}</li>
          {{ end }}
        </ul>
        {{ .Content }}
      </div>
    </article>
  </div>
{{ end }}

At the start of the file, the header content is pulled in from another file stored in layouts/partials. Using this ‘partial’ allows you to build templates up from several parts.

The line {{ humanize .Section | upper }} cleans up the value of .Section - the name of the content folder - removing hyphens and spaces and turns it to uppercase. It’s automatically pluralised by Hugo so, for example, review becomes REVIEWS in the built site.

From Hugo, the scope, the content and the dot, I figured out that I could switch context and render out each parameter in turn. The use of with means that a parameter will only be used if it has content. In this way, I can simply leave these fields blank and they won’t be rendered:

<ul>
{{ with .Params }}
    {{ with .Release }}
      <li>Original Release: {{ . }}</li>
    {{ end }}
    {{ with .Directors }}
      <li>Director(s): {{ delimit . ", "}}</li>
    {{ end }}
    {{ with .Genre }}
      <li>Genre(s): {{ delimit . ", " }}</li>
    {{ end }}
    {{ with .Producers }}
      <li>Producer(s): {{ delimit . ", "}}</li>
    {{ end }}
    {{ with .Screenwriters }}
      <li>Screenwriter(s): {{ delimit . ", "}}</li>
    {{ end }}
    {{ with .Alternative_Titles }}
      <li>Alternative Title(s): {{ delimit . ", "}}</li>
    {{ end }}
{{ end }}
</ul>

Since some of my parameters could have several values, I made these lists in the Front Matter. The use of {{ delimit . ", "}} above means that the current context (i.e. the list of Directors; represented by the dot) will be rendered with a comma between items where there is more than one item in the list.

Back to Top

Multiple sections on the homepage

After creating my Project Ghibli posts, I was slightly surprised to find that my homepage only showed my review Section content rather than a mix of posts from all Sections I had created.

It turns out that in themes it’s common and recommended to use the following:

{{ $section := where .Site.RegularPages "Section" "in" $mainSections }}

This stores a list of all the user created pages (.Site.RegularPages), however it defaults to the Section with the most pages. Since I had created more Project Ghibli posts than regular posts at this point, those were what was being returned.

Since my theme uses this approach, I needed to override $mainSections with the Sections I wanted to include. So in my config.toml file I added this:

[params]
mainSections = ["posts", "review"]

A word on List Pages

Something that tripped me up was receiving a 404 error when viewing list pages when there were no posts available. I expected to see at least a regular page with no main content. Thanks to the Hugo forum, it turns out that while a Section is defined from a collection of content, if there is no content (i.e. it’s in draft), there is no section. This can be remedied by having an empty _index.md file in each section (note the underscore).

Back to Top

Creating the Navigation Menu

Hugo comes with a built in menu system. The easiest way to create a simple navigation is to add SectionPagesMenu = "main" to config.yaml. This will set the menu named “main” to include all Sections. These can then be looped through in a template using {{ range .Site.Menus.main }} for display. The Ananke theme already does it like this, for example:

{{ if .Site.Menus.main }}
  <ul class="pl0 mr3">
    {{ range .Site.Menus.main }}
    <li class="list f5 f4-ns fw4 dib pr3">
      <a class="hover-white no-underline white-90" href="{{ .URL }}" title="{{ .Name }} page">
        {{ .Name }}
      </a>
    </li>
    {{ end }}
  </ul>
{{ end }}

Adding SectionPagesMenu = "main" to config.toml is all you need to do to use this with Ananke.

I didn’t necessarily want all sections in my navigation though and I wanted to be able to rename some of them. Instead of using SectionPagesMenu, individual menu items can be defined in config.toml:

[menu]
[[menu.main]]
  identifier = "posts"
  name = "Blog"
  title = "All Posts"
  url = "/posts/"
  weight = 1

[[menu.main]]
  identifier = "review"
  name = "Project Ghibli"
  title = "Project Ghibli"
  url = "/review/"
  weight = 2

The documentation doesn’t explicitly say it, but in TOML format you define [[menu.main]] items for each of the menu items.

The identifier and url must match the name and location of the Section. The name is the name displayed, though i’m not sure yet what purpose title serves. The weight controls the order, with lower numbers coming first (they can be negatives too).

Since I later made all my posts (including reviews) live inside the posts section, I included them in the menu seperately by using the url for each category:

[[menu.main]]
  name = "Project Ghibli"
  url = "/categories/project-ghibli"
  weight = 1

[[menu.main]]
  name = "Travel and Events"
  url = "/categories/travel"
  weight = 2

My About page is not defined in config.toml since it I already defined this in the page’s Markdown file by adding menu: "main" to its Front Matter. I didn’t include a weight so this automatically comes after the other two menu items:

---
title: "About"
description: "About Daniel Mclaughlan — Accessibility and Usability Consultant"
menu: "main"
---

Back to Top

Creating Categories and Tags

Hugo has built in support for Taxonomies and it comes with two defined by default: categories and tags. This took awhile for me to get my head around since, unlike Wordpress, they are functionally both just lists of stuff. Again, the Hugo forum helped:

..perhaps it should be explicitly stated that the words ‘tags’ and ‘categories’ are simply possible names for your taxonomies that have no inherent functional difference in Hugo - i.e. they’re both just lists of stuff that could be called anything.

This is potentially confusing especially for people coming from Wordpress where ‘tags’ and ‘categories’ do have a specific meanings and different functionality. Categories being potentially hierarchical and only available on posts where tags are available on both posts and pages and are typically used in a more ad-hoc ‘folksonomy’ manner.

You can use these simply by setting them in the content’s Front Matter:

categories:
- Travel
tags:
- tea
- cafe
- madrid

You can also create custom taxonomies by defining these in config.toml. If you still want to use the built-in categories and tags taxonomies, you must define these too. I don’t have any custom taxonomies on this site, however in another project i’m working on I create some for the metadata about different games:

[taxonomies]
  category = "categories"
  tag = "tags"
  genre = "genres"
  developer = "developers"
  publisher = "publishers"
  platform = "platforms"
  feature = "features"

Viewing the site locally

The last thing to do to see my site in action, locally at least, is to start a local webserver. Hugo makes this simple:

hugo server -D

The -D flag tells Hugo to include content marked as a draft. By default, Hugo will not build drafts, but we can override that behaviour during development with this flag. Here is a list of hugo server options.

Once the process is complete, you will get a summary of what was created and the URL of where to view the site at http://localhost:1313/

The wonderful thing about this is now any changes made to your files will cause Hugo to instantly rebuild the site so you will see changes almost immediately. This is the Static Site Generator in action. By default, Hugo only rebuilds what has changed (Fast Render Mode) but if you prefer this can be disabled using the --disableFastRender flag to rebuild the whole site each time.

Back to Top

GitHub and Deploying the site to Netlify

When I created the project, using git init in my project directory set it up as a git repository. To deploy the site to Netlify, I first had to commit my local repository to GitHub.

Following the advice of forum posts, I created a .gitignore file in the root directory of my project to have Git avoid tracking the /public and /resources folder that would be generated when the site is built (since Netlify will take care of this):

# Hugo default output directory
/public
/resources

## OS Files
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/

# OSX
.DS_Store

I then created a remote repository on GitHub and pushed the changes in my local repository to it. See Adding an existing project to GitHub using the command line

Next, I logged into Netlify and clicked New Site from Git button before authorising Netlify to connect to my GitHub account. I chose the repository ‘personal-blog’ and branch master. Netlify already included the hugo build command and public directory for publishing. See Hosting on Netlify for more information.

Now my site’s files are stored on Github and any time commits are made - such as when I add a new blog post - Netlify will rebuild the site.

(Again, remember that posts that are draft: true won’t show up in production)

Back to Top

Final Thoughts

Overall the process of creating a site with Hugo has been really nice. While i’ve gone into a lot of detail in this post, this is only because I tweaked lots of options. In reality creating the initial site with Hugo, GitHub and Netlify only took a few minutes.

There are still some bits i’m working on that I will cover in a later post:

  • Using a Content Management System
  • Hosting and Using Images
  • Implementing a Comments system

https://forestry.io/blog/up-and-running-with-hugo/

https://www.11ty.dev/

https://gohugo.io/

https://www.staticgen.com/

Featured Image photo by Jess Bailey on Unsplash

hugo static site generator

1 Comments

Be the best essay writer as it may, in the event that you truly don't concur, stand firm. It's your story, all things considered. (If it's not too much trouble note: on the off chance that you demonstrate it to three individuals and they all state something very similar yet you don't concur, they're most likely right. Sorry!) By: https://www.writemyessays.org.uk/

- By ThomasMore on Wed, 17 Jun. 2020, 11:59 UTC

Leave a Pawprint

Confidential, will not be shared with anyone or published here.