This is a tutorial for a basic setup and usage of Hugo static site generator. Here you will get an unofficial “quick-start” and I will discover new features and challenge myself to create my first ((this)) post. The article aims to give a basic-to-advance overview of Hugo’s functionality, assuming that you know what it is. This text may contain errors due to misunderstanding and lack of experience, but I will try my best to summarize everything.

Basic usage

Installation

I recommend installing Hugo from their releases rather than your package manager. Hugo version from Debian repository did not support my “PaperMod” theme, thus use GitHub. You may download an archive and unzip it into */bin or get a .deb (if you are on a Debian-based distro) and install it using sudo dpkg -i hugo.deb .

Quick start

To create a new Website, run the following commands:

hugo new site SITE_NAME
cd SITE_NAME

This will create a directory SITE_NAME and initialize basic configuration to directly run Hugo.

You can also choose a theme for your website from Hugo’s catalog. You can apply it using following commands:

git init
git submodule add THEME_GIT_URL themes/SIMPLE_THEME_NAME
echo "theme = 'SIMPLE_THEME_NAME'" >> config.toml

By using git submodule you can later easily update all your website’s git dependencies by running git submodule update

Personally, I prefer yaml over toml. Therefore, all further configuration will be written in yaml. You may also want to convert your config into other format by using this or any other converter and creating a new config.yml with new configuration. You can move or remove the old config.toml. Alternatively, you can use the same converter to convert further configuration back to toml.

Adding content

After setting up basic configuration, time to add a first page. Run hugo new posts/my-first-post.md from your root website directory to create a new post. Alternatively, you may create a file yourself, but you would need to add a header to the post yourself:

---
title: "My First Post"
date: 2022-11-20T09:03:20-08:00
draft: true
---

You can also create a separate directory for your new post. It can be used to store assets and other post-related data. Just specify a folder name between posts/ and your markdown file in hugo new command. You can add basic content to your new markdown file under the header:

## Introduction

This is **bold** text, and this is *emphasized* text.

Visit the [Hugo](https://gohugo.io) website!

Now you can start your Hugo debug server:

hugo server -D

📘 Note: If the server is running on a different machine, add these parameters to the command: --bind 0.0.0.0 --baseUrl MACHINE_IP

The -D parameter also publishes drafts, as specified in post’s header; if you wish to exclude them, remove this argument.

Now you can edit config.yaml and change some of the basic configuration (like website name and base URL) and continue.

Publishing your website

hugo server is only used for creating drafts, to see live changes. When you are done with editing, you will have to use an external web server to publish your files. By simply running hugo command from the root of your website, it creates HTML from markdown files in public directory. You may want to clear it beforehand yourself, since Hugo doesn’t do this automatically and may leave rest files from previous runs there. Afterward, we will use NGINX to serve the files:

server {
    root HUGO_WEBSITE_PATH/public;
    listen 80;
    listen [::]:80;

# Uncomment to use SSL
#    listen 443 ssl http2;
#    listen [::]:443 ssl http2;
#    ssl_certificate SSL_PUBLIC_KEYCHAIN;
#    ssl_certificate_key SSL_PRIVATE_KEY;

    server_name DOMAIN_NAME;
}

Advanced usage

This is where the advanced part begins. It contains documentation entries, my Internet research and discoveries, which I summarized and structured.

Site structure

To continue, we need to understand the basic structure of a Hugo website. Further, we will be accessing resources from all around the website and this knowledge will simplify our lives. This tree from official documentation perfectly explains the structure:

.
└── content
    └── about
    |   └── index.md  // <- https://example.com/about/
    ├── posts
    |   ├── firstpost.md   // <- https://example.com/posts/firstpost/
    |   ├── happy
    |   |   └── ness.md  // <- https://example.com/posts/happy/ness/
    |   └── secondpost.md  // <- https://example.com/posts/secondpost/
    └── quote
        ├── first.md       // <- https://example.com/quote/first/
        └── second.md      // <- https://example.com/quote/second/

An example of a post with assets:

content
└── post
    ├── first-post
    │   ├── images
    │   │   ├── a.jpg
    │   │   ├── b.jpg
    │   │   └── c.jpg
    │   ├── index.md (root of page bundle)
    │   ├── latest.html
    │   ├── manual.json
    │   ├── notice.md
    │   ├── office.mp3
    │   ├── pocket.mp4
    │   ├── rating.pdf
    │   └── safety.txt
    └── second-post
        └── index.md (root of page bundle)

One thing to add is that you can override a post URL by adding a url property to page variables.

Customizing appearance

Each theme has its own configuration options and possibilities, and they are described in their repository. However, most configuration shares common concepts, which I will be going to explain. You will learn them on an example configuration of “PaperMod” theme.

Site variables

Site variables are defined in config.yaml / config.toml section. They are accessible in all pages and are applied globally, if they affect theme’s appearance. Most variables have to be put into params section. Example:

author: Me
hideFooter: true	# PaperMod specific, hides the footer with unneeded text
showtoc: true		# Always show table of contents
tocopen: true		# Open TOC on page load
fuseOpts:			# Parameters for PaperMod search page, read further
  isCaseSensitive: false
  shouldSort: true
  location: 0
  distance: 1000
  threshold: 0.4
  minMatchCharLength: 0
  keys: ["title", "permalink", "summary", "content"]

Here are some additional commonly used variables:

outputs:	# Needed for PaperMod search
  home:
    - HTML
    - RSS
    - JSON	# is necessary
menu:		# Navbar menu entries
  main:
    - name: Search
      pageRef: /search
      weight: 2			# Higher weight means more to the right
    - name: Tags
      pageRef: /tags
      weight: 1
taxonomies:				# Only use one taxonomy - tags (disable categories)
  tag: tags
  • outputs defines output formats for given pages. For the search to work, we have to include JSON additionally to default HTML and RSS output formats for main (home) page.
  • menu property defines menu entries. main, in this case, is the main navigation bar menu visible on all pages. The structure of this property is pretty straightforward, so I will skip explanation. There is also another way to add menu entries using page variables, but it’s easier to manage them if they are all in one place (config.yaml).
  • taxonomies define pages for “tags”. By default, there are two categories: “tags” and “categories”. You can use “tags” for more detailed description of a page and “categories” for more general, like “tutorial” or “linux”, for example. These are later declared in page variables and appear under /tags or /categories URL. I disabled “categories” by defining “tags” as the only taxonomy, so I don’t have the second page.

If you want to learn more, here is a list with all basic Hugo site variables. If you also use “PaperMod” theme like I do, here is the theme specific configuration.

Page variables

Page variables, on the other hand, are only accessible from a single post. They specify its “metadata” and some of the internal parameters (like expiry date), which are then processed by Hugo. By default, when creating a post with hugo new it automatically appends basic variables to the top of the document:

---
title: "My First Post"
date: 2022-11-20T09:03:20-08:00
draft: true
---

It has YAML syntax (since opened and closed by ---). I personally prefer adding two more variables:

url: articles/first-post
tags: [tutorial,educational]
  • url is used to override the default path. Even though my-first-post.md is in posts folder and should be accessible at posts/my-first-post, it will be on articles/first-post.
  • tags are keywords to speed up the search for posts. Read above to learn more about Taxonomies.

References

Resources

When we speak about resources, we mostly understand videos and images. For some reason, official documentation doesn’t mention usage of basic markdown ![alt](src) construction, but instead suggests variables and Go-constructions. Unfortunately, you cannot use them directly in your markdown posts. You would have to create a shortcode, which accepts parameters and then passes them to templates… Forget it. For most use cases, it’s enough to just use basic markdown image construction with a relative path to an it and a YouTube shortcode for videos. That’s it. Example:

Example image

![First image I found on my laptop](images/orig.jpg)

First image I found on my laptop

YouTube example:

\{\{< youtube UTUMZfNP9dY >\}\}

Where “UTUMZfNP9dY” is the YouTube video ID (in the URL). I had to escape braces to prevent Hugo from interpreting the above code snippet as a real video. Remove backslashes for the construction to work.

Official documentation provides a really good example:

.
└── content
    ├── about
    |   ├── _index.md
    |   └── credits.md
    ├── pages
    |   ├── document1.md
    |   └── document2.md    // has anchor #anchor
    ├── products
    |   └── index.md
    └── blog
        └── my-post.md
\{\{< ref "document2" >\}\}             // <- From pages/document1.md, relative path
\{\{< ref "document2#anchor" >\}\}      
\{\{< ref "document2.md" >\}\}          
\{\{< ref "document2.md#anchor" >\}\}   
\{\{< ref "#anchor" >\}\}               // <- From pages/document2.md
\{\{< ref "/blog/my-post" >\}\}         // <- From anywhere, absolute path
\{\{< ref "/blog/my-post.md" >\}\}
\{\{< relref "document" >\}\}
\{\{< relref "document.md" >\}\}
\{\{< relref "#anchor" >\}\}
\{\{< relref "/blog/my-post.md" >\}\}

As you can see, ref / relref (are identical and) are also shortcodes and thus can be used in Markdown. Example:

[First image I found on my laptop](\{\{< relref "#example-img" >\}\})

First image I found on my laptop

Each heading gets a unique ID during site building. It consists of its text in lower case, joined with ‘-’ character. For example, “Links and cross references” has an ID of “links-and-cross-references”. If there are multiple headings with the same text, Hugo will append a number at the end, e.g “heading-1”, “heading-2”… You can override the ID of an element by adding {#new-id} after heading text.

Summary

In this article, we discussed basic-to-advanced features of Hugo static website generator. It will be enough to build your own blog, technical documentation or a tutorial website like I have. However, there is a lot more to learn. Hugo has huge templates and functions sections, which allow you to customize your site even more. I do not have experience using them, and it’s a big topic which requires a separate post. Right now I’m happy with the configuration listed above, but if I discover something new, I will tell you. Stay tuned!