Welcome to Hugo!
You’ll learn to hate love it! First, let’s get the basics laid out.
Setting things up
This tutorial assumes a Linux-based OS, see the official Quick Start guide for how to setup Hugo on your OS.
For demonstration purposes, I’ll assume the website is titled “Basic”:
- Find a place for your website, I tend to put my websites under
Public/
to keep them out the way. - cd into
Public/
, then run hugo new site Basic to create your website and its directory structure. - cd into
Public/Basic/
.
The website preview is left until everything is configured, so don’t run hugo server yet.
Directory structure
Each directory has a particular role in how Hugo generates your website, however, you only need to care about some of them for basic functionality. That being said, here’s a rough overview of them all:
archetypes
: Defines the default markdown for posts & post bundles created with hugo new content.assets
: Contains globally accessible content intended for processing, including images, preprocessor stylesheets, and templates.content
: Contains the markdown files for all pages, including sections, taxonomies, etc.data
: Contains files with data intended to be parsed by functions, you won’t need to worry about it.layouts
: Contains the default HTML content, partials, and shortcodes that will consume the markdown files undercontent/
.public
: The output directory for hugo or hugo build. It does NOT clear itself when builds are ran repeatedly, so make sure it’s empty before rebuilding.resources
: Contains processed resources, for internal use.static
: Contains static content not intended to be processed, including normal stylesheets, favicons, etc.themes
: Contains themes, which themselves contain duplicate directory structures that can be easily overridden by content defined outside the theme in the usual directories.
In reality, the two directories you’ll be primarily working with are content
and layout
, since they form the basis of the website content, and how Hugo processes that content. The other directories are important, but tend to be updated less frequently by their nature (the likely exceptions being stylesheets).
From this point, you’ll be building up the website into a minimal, functional demo that you’ll be able to be preview by the end.
Archetypes
There are three different types of archetypes you should be aware of (see the Page bundles documentation for more information):
- The default archetype:
archetypes/default.md
. - Post/Leaf bundles:
archetypes/post-bundle/index.md
orarchetypes/section/index.md
.- Example command: hugo new content blog/post.
- Branch bundles:
archetypes/taxonomy/_index.md
.- Example command: hugo new content content/tags.
Pay close attention to when the underscore prefix is included or not in the filename.
A notable feature of bundles is that they can have page resources, where extra files can be added such as header images, stylesheet overrides, etc (e.g. content/blog/post/override.css
), however, only Leaf bundles support nesting the resources under another directory (e.g. content/blog/post/images/header.png
).
This is because Branch bundles support nesting Leaf bundles and even other Branch bundles under themselves, so content/branch1/branch2/images/
would treat images/
as another bundle, and it’d additionally treat it as invalid since it has no index.md
nor _index.md
file.
Here’s an example archetype file to get you started:
Don’t worry about the stuff inside the
{{ ... }}
template blocks for now, you’ll start to learn how they work in the Partials section.
Defining archetypes for default.md
post-bundle/
, blog/
, tags/
, and series/
is recommended so you don’t need to manually define page kinds with the -k option (e.g. hugo new content -k post-bundle blog/post). Notice that you don’t need to include content/ in the path if you’re defining a page under a section, taxonomy, or term; you only need to include it if you’re defining a page such as content/about.md.
Content
Here’s where the fun begins!
content/_index.md
defines the markdown content of the homepage. It’s completely optional, and usually unnecessary unless you want to include any of the markdown content or front matter in your homepage. It’s worth noting that resources stored undercontent/
are considered page resources for the homepage (e.g.content/header.png
).content/*.md
defines posts not under any section, such as an about page atcontent/about.md
. Do NOT create post bundles undercontent/
since, for example,content/about/
will be considered a section instead of a post bundle.content/directory/
can define either a section or a taxonomy, depending on whether the directory has the name of a taxonomy defined in Hugo’s config (which will be explained later).content/directory/_index.md
defines the markdown content of the section or taxonomy. It’s typically used for setting page variables like .Title or .Description.content/taxonomy/term/_index.md
defines the markdown content of a term in a taxonomy (e.g.tags/first/
is a term under the taxonomytags/
). It’s typically used for setting page variables.
Hugo is case-sensitive, even if HTTP/DNS is not, so if you create
content/Blog/
the .Permalink URL will be output ashttps://basic.tld/Blog
.
For this next part, you’ll be creating various markdown files for an about page, a section named “Blog”, a post named “My First Post”, and a tag named “First”. Remember that you can use hugo new content to do this (e.g. hugo new content content/example.md or hugo new content blog/_index.md).
Layout
Partials
Before we get to the HTML files, let’s create some partials
to act as building blocks:
Notice that
</body>
and</html>
aren’t present in the header partial, they’re instead included in the upcoming footer partial.
Hugo uses Go’s template language for inserting content into HTML & Markdown files, like for inserting the variable .Site.Title into the header partial, which can be both a powerful tool and a moderate headache to deal with.
There’s a bunch more template stuff in this footer partial compared to the header partial, so I’ll try to break it down:
- .Site.Title (variable): Takes the value of
title
from the Hugo config file (which I’ll explain later). Similar story for .Site.Copyright. now.Year
(variable & function): Takes the current date (now), and formats it as a four digit year number (.Year
).- This is the same as the function
now.Format "2006"
, see the.Format
documentation for all available values.
- This is the same as the function
with
(function): If its variable or function is absent, empty, or returns false, its content is excluded. Unlike theif
function, which behaves similarly,with
rebinds its content’s context to the variable or function it’s acting on.{{ with .GetPage "/about" }} content {{ end }}
: “content” will be inserted if/about
exists.{{ with .GetPage "/about" }}{{ .Description }}{{ end }}
: The context is rebound to/about
, so .Description will be the about page’s description, and not the current page’s description.
Managing context rebinding for functions like with
or range
is tricky to summarise, so don’t worry about it for now; the next blog post will dive deeper into how it works.
Layout Content
Now, let’s use those partials in the homepage:
The dot defines the partial’s context. You don’t need to worry about the details, I’ll cover this in the next blog post, but, don’t leave it out.
Other pages, like sections, taxonomies, terms, and single (blog posts) are defined under layouts/_default/
, however, we’re only going to define single.html
since that’s all we need for a functioning preview of a post.
.Content is the content of the markdown post you created.
To define term pages, you can either create
layouts/_default/term.html
to define a layout for all terms, or, using the taxonomy “Tags” as an example, you can createlayouts/_default/tag.html
(notice how it’s singular, I’ll get to that later).
Website Config
Since I’ve put off explaining some of the weird details, like how Hugo determines the difference between sections or taxonomies, or why tag.html
is singular, let’s talk about Hugo’s config file:
Hugo supports JSON, YAML, or TOML configs, both for the config file and markdown front matter. All config examples you see will use TOML, see the Hugo configuration documentation for more information.
To quickly explain the most important options:
baseURL
defines the root URL for your website, which is used in absolute links likehttps://basic.tld/header.png
, as opposed to relative links like./header.png
.title
sets the .Site.Title variable’s value.[params]
defines site-wide variables accessible at .Site.Params.param-name.['Item1, Item2']
defines a list of items.
[taxonomies]
defines which directories undercontent/
should be treated as taxonomies.- Practically speaking, the first value is the name you specify under
layouts/
(e.g.layouts/tag.html
), while the second value (in single-quotes) is the name you specify undercontent/
(e.g.content/tags/_index.md
).
- Practically speaking, the first value is the name you specify under
Seeing the result
Everything should be setup now. So, check that your working directory is set to Public/Basic/
, and then, run hugo server and navigate to the URL it prints to see a preview of your website.
The website should update whenever you make changes, however, there are a number of cases where it doesn’t (resource copying, browser caching, etc). If your changes aren’t showing up, quit hugo server, restart your browser, and then restart the server to ensure the preview is correct.
Since there’s no navigation links yet, besides the about page link in the footer, you’ll have to manually enter the URL for blog/my-first-post
to navigate to it.
From here, your imagination is the limit (excluding functions Hugo denies for security reasons)! I personally recommend looking at Eric Murphy’s starter theme if you want a more ready-to-use starter to base your website on. In the next blog post, I’ll show how to use the range
function to create navigation pages, and how to handle context rebinding issues.