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 includeJSON
additionally to defaultHTML
andRSS
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 thoughmy-first-post.md
is inposts
folder and should be accessible atposts/my-first-post
, it will be onarticles/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)
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.
Links and cross references
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!