Who is Hugo? Or in this context, what is Hugo?
It is a software to create websites. It takes some input (markdown files) and generates a website from it (html, css and other files). This type of website software is called a static site generator. It is static, because the built website is literally just some files that are being sent to the visitor. Many modern websites generate websites on-the-fly when you visit them, based on your user, your language, your location. This makes them usually slower and also more complex. A static site generator is quite simple and fast and in the case of Hugo, very adaptable customizable!
You should try it out! Visit the Hugo homepage and make sure to check out some of the themes.
This website is the first time of me using Hugo. So I am learning on the go. As a living documentation, I am growing this page of all I understand of Hugo.
Helpful Links and Resources
Basic Concepts
Main folders:
content/ ← what you say
layouts/ ← how it looks
static/ ← files copied with no changes
Hugo automatically assigns a kind to each page.
- home → the front page, nothing else
- page → single content file, static page or blog post
- section → folder like `posts/` with multiple pages inside
- taxonomy → tags, categories, etc
Example:
content/posts/hello.md
This is section: posts, kind: page
content/posts/_index.md
This is section: posts, kind: section (list page)
Terms, Taxonomies and Sections
Section
A section is a landing page for all pages inside a folger. E.g. all files in posts are a section. The layout section.html is there to show all files inside one directory, or in Hugo speak, one section. For this, the _index.md is used. Typical use case would be an index of all blog entries.
Lets take this example directory structure:
/content/posts
_index.md
/01-hello-world
index.md
/02-some-thoughts
index.md
03-a-project.md
Now visiting example.com/posts/ would use the section.html layout fill it with content using content/posts/_index.md and all folders and files inside the posts directory.
The name of the section can be changed or defined in the _index.md in the frontmatter using the title = 'Section Name' property. This will also be the one used in the term overview later.
Taxonomy and Terms
A taxonomy is a way to categorize things. A sorting system basically. This can be anything, e.g. a category, tag or topic. The values of one of those taxonomies are its terms.
For example, I might have the taxonomy tags and its values are tech, art and music. This would leave me with the the following urls:
/tags/ <-- Taxonomy page made from taxonomy.html layout. Shows all terms of the taxonomy.
/tags/tech <-- Term page made from term.html layout. Shows all pages with this term.
/tags/art <-- Term page made from term.html layout. Shows all pages with this term.
/tags/music <-- Term page made from term.html layout. Shows all pages with this term.
To fill a e.g. a term page with custom content, create the following file /content/<taxonomy-name>/<term-name>/_index.md and fill it e.g. with:
---
title: 'tech'
---
These are all the things related to technology.
Same applies for a taxonomy.
Leaf vs Branch bundles
leaf bundle
A leaf bundle is a directory that contains an index.md file and zero or more resources. Analogous to a physical leaf, a leaf bundle is at the end of a branch. It has no descendants.
branch bundle
A branch bundle is a directory that contains an _index.md file and zero or more resources. Analogous to a physical branch, a branch bundle may have descendants including leaf bundles and other branch bundles. Top-level directories with or without _index.md files are also branch bundles. This includes the home page.
Images in page bundle, assets or static folder
Images can live in three directories: page-bundle, assets and static.
/content/
/page-bundle
/index.md
/picture01.png
/assets/
/img/
/picture02.png
/static/
/img/
/picture03.png
The page bundle makes the most sense, when the image is only used in this page. Then it is called with {{ with .Page.Resources.Get $path }}.
The static directory only makes sense for pictures that are final, should not be touched by Hugo and need stable links. This is great for logos, favicons, etc.
The assets folder is for global resources that can still be accessed by the Hugo pipelines, e.g. resizing or fit. This can be called with {{ with resources.Get $path }}.
Context object
Every template gets a context object: . (dot)
Common fields:
.Title.Content.Date.Params.Kind.Section
In a loop, the context . switches to the page
<section>
<h2>
<a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
</h2>
</section>
Inside range:
-
.is no longer the homepage -
.is each page in turn -
.Contentis already HTML -
never wrap it in
<p>or escape it
Creating new pages via Hugo CLI
# As single files
hugo new <section name>/'<post title>'
# As leaf bundles
hugo new <section name>/'<post title>'/index.md
Variables
They:
- start with
$ - exist only inside the current template / block
- are immutable (you don’t “change” them later)
Templates and Partials
A partial is a function that returns HTML. It takes content, usually from markdown files and builds HTML with it.
Other
Dates in Hugo
2026-01-01T17:00:00+01:00
YYYY-MM-DDTHH:MM:SS+HH:MM (Time zone difference)
// The T delimits the date from the time
Link to sections with <a> tags
- Rightclick on heading, inspect and copy ID
- Use this
<a href="/section/postname#id">
Setup new site
When installing a new theme and asked to enter hugo mod init github.com/USER/REPO, any link-like string can be used (excluding https://), but it makes sense to use the link to the repo.
Creating a theme
- home.html and index.html is the same
- /layouts/ containts templates
- /layouts/partials/ contains partial templates
Shortcodes
A shortcode is a template invoked within markup, accepting any number of arguments. There are some embedded shortcodes, but you can also create custom ones.
Show Hugo shortcodes in code blocks
If you come to the point that you want to show others how to use a shortcode inside a code block, Hugo still convertes them, even inside code block. To prevent this, a comment indicator needs to be added to them. So it will be {{</*.
Create a custom shortcode
- Create a file in
layouts/shortcodes/name.html. - Create the shortcode, e.g. like
{{ with resources.Get (.Get "src") }} <audio controls preload="auto" src="{{ .RelPermalink }}"></audio> {{ end }} - Call the shortcode within the file with
{{</* audio src=/audio/test.mp3 >}}
Delegate a shortcode to a partial
If you want something to be executable as a partial and as a shortcode, create a partial as the base logic. E.g. here is the tag partial from my theme:
{{/* Small tag, like an inline callout */}}
{{- $text := .text -}}
{{- $style := .style -}}
{{- if $text -}}
<span class="tag tag--{{ $style }} tag--{{ $text | urlize }}">
{{ $text }}
</span>
{{- end -}}
Then, create a shortcode that delegates to the partial, or calles it. E.g.:
{{/* Delegate content to the partial */}}
{{ partial "tag.html" (dict
"text" (.Get "text" | default (.Get 0))
"style" (.Get "style")
) }}
This way it can be used both as an partial with {{ partial "tag.html" (dict "text" "pending ⏳" "style" "note") }} or as a shortcode with {{< tag text="pending ⏳" style="note" >}}.
Guides
Selfhost and serve fonts with a Hugo theme
As I somehow have strong opinions about aestetics, being able to select the right font was important. Here are the steps I took:
-
Download the files
- Go to google-webfonts-helper and search for the fonts you like
- Select all the variants you want, download the fonts at the bottom and place them in the Hugo project under
/static/fonts/font-family-name/.
-
Load the fonts with CSS
- This will be done with a layout partial inside the theme. Create a file
theme/layouts/_partials/fonts.html. - Go back to the website, enter
/fonts/font-family-name/as a folder prefix and copy the generated css code. - Paste the css into the
fonts.htmlpartial like so and make sure to check if the paths are correct:<!-- Template that loads the fonts which are exposed in the /static/fonts/ directory --> <style> /* IBM Plex Mono */ /* ibm-plex-mono-regular - latin */ @font-face { font-display: swap; font-family: 'ibm_plex_mono'; font-style: normal; font-weight: 400; src: url('/fonts/ibm_plex_mono/ibm-plex-mono-v20-latin-regular.woff2') format('woff2'); } /* ibm-plex-mono-italic - latin */ @font-face { font-display: swap; font-family: 'ibm_plex_mono'; font-style: italic; font-weight: 400; src: url('/fonts/ibm_plex_mono/ibm-plex-mono-v20-latin-italic.woff2') format('woff2'); } /* ... more font variants */ </style> - Now the partial needs to be included inside the hugo page. For this, edit
baseof.htmland include the partial inside the header, like so:<!DOCTYPE html> <html lang="{{ site.Language.LanguageCode }}" dir="{{ or site.Language.LanguageDirection `ltr` }}"> <head> {{ partial "head.html" . }} {{ partial "fonts.html" . }} <!-- here it is loaded --> </head> <body> <div class="site-wrapper"> <header> {{ partial "header.html" . }} </header> <main> {{ block "main" . }}{{ end }} </main> <footer> {{ partial "footer.html" . }} </footer> </div> </body> </html>
- This will be done with a layout partial inside the theme. Create a file
-
Select the font for the site
- The font is loaded in css and available to be used, but currently it is not used yet. We need to say we want to use it. To make it nice and configurable, we will make use of the
hugo.tomlto decide which fonts are being used. For this, edit/hugo.tomland add:[params.fonts] body = "ibm_plex_mono" # This must match the `font-family` in `fonts.html` head = "ibm_plex_sans" code = "ibm_plex_mono" - Now we read the selected fonts from the
hugo.tomland set them as css variables to be used lates. Add the following at the bottom offonts.html, which we created earlier:</style> /* ... font face load */ /* Load configuration and save as variables */ :root { --font-body: "{{ .Site.Params.fonts.body }}"; --font-code: "{{ .Site.Params.fonts.code }}"; --font-head: "{{ .Site.Params.fonts.head }}"; } </style> - And lastly, we select these fonts to be used inside the css. For this, go to your css, in my case
theme/assets/css/main.cssand load the fonts like so:body { /* ... */ font-family: var(--font-body), system-ui, sans-serif; /* ... */ } pre, code { font-family: var(--font-code), monospace; /* ... */ } h1, h2, h3 { /* ... */ font-family: var(--font-head), system-ui, sans-serif; }
- The font is loaded in css and available to be used, but currently it is not used yet. We need to say we want to use it. To make it nice and configurable, we will make use of the
-
Add new fonts later. To add new fonts later, you just need to
- Download and put them in `/static/fonts/font-family/
- Load them in
/theme/layouts/_partials/fonts.html - Select it in
hugo.toml