Hugo
Page Bundles
Hugo broadly splits all pages into two types:
- List which have
_index.md
content files. These are also called branch bundles. - Single which have
index.md
content files. These are also called leaf bundles.
Page kinds
The PAGE.Kind method shows any Hugo page to be one of four types:
- home (branch)
- page (leaf)
- section (branch)
- taxonomy (branch)
- term (branch)
All but “page” are considered branches, making their default template layouts/_default/list.html
.
Only “page” defaults to layouts/_default/single.html
.
A minimalist layouts folder which can serve all 5 kinds is:
layouts
└── _default
├── list.html
└── single.html
To keep things DRY: base templates and blocks
layouts
└── _default
├── baseof.html
├── list.html
└── single.html
The above list.html and single.html duplicate the basic HTML5 skeleton which could be moved to baseof.html which would then be used by both of them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .Kind }} — {{ .LinkTitle | safeHTML }}</title>
</head>
<body>
{{ block "main" . }}{{ end }}
</body>
</html>
For each {{ bock "whatever" . }}
there is a corresponding {{ define "whatever" . }}...{{ end }}
in
the template files.
List Pages
The list template file layout/_default/list.html
is the default for numerous Hugo page types.
List pages typically range over the Pages, ie subdirectories, of
wherever the _index.md
file is located. There are various ways to
sort the order in which the pages are listed.
{{ define "main" }}
<ul>
{{ range .Pages.ByLinkTitle }}
<li>
<a href="{{ .RelPermalink }}">{{ .Date.Format "2006-01-02" }} | {{ .LinkTitle | safeHTML }}</a>
</li>
{{ end }}
</ul>
{{ end }}
Single Pages
{{ define "main" }}
<h2>{{ .Title | safeHTML }}</h2>
<div>{{ .Content }}</div>
{{ end }}
Overiding the defaults
Given how long the list for the various page kinds in Hugo’s template lookup order, my suggestion is start at the bottom and work up. If the generic list.html template needs something more specific, then use the second last filename. If a section or taxonony along with its pages or terms needs a more specific template, give it its own subdirectory in the layouts directory.
Taxonomy
layouts
└── _default
├── baseof.html
├── list.html
├── single.html
└── taxonomy.html
<ul>
{{ range .Data.Terms.Alphabetical }}
<li><a href="{{ .Page.Permalink }}">{{ .Page.Title }}</a> {{ .Count }}</li>
{{ end }}
</ul>
Term
layouts
└── _default
├── baseof.html
├── list.html
├── single.html
└── term.html
Hugo will use layouts/_default/term.html
if it exists instead of layouts/_default/list.html
.
I found it a little confusing that “term” is not a leaf considering from the perspective of a reader
of a Hugo website, /section/page/
and /taxonomy/term/
are indistinguishable.
With a bit more
experience, thinking of term pages as lists made sense since whatever “term” might occur in several pages.
Taxonomy Templates
Each of the blocks in the baseof.html file could have corresponding define stanza in their home, list or single templates. If there isn’t a corresponding stanza, the baseof.html could contain default code to use.
{{ define "name" }}
...
{{ end }}
Home
File names
Historically layouts/index.html
, but alternatively layouts/home.html
which is handier to
avoid confusion with index.html files in content directories. Better yet, layouts/_default/home.html
to keep all the templating out of the root layout directory.
Can have separate baseof layouts/_default/home-baseof.html
.
Taxonomies
The template can be layouts/_default/terms.html
, or confusingly layouts/_default/taxonomy.html
taxonomy.html is the equivalent of single.html, and terms.html the equivalent of list.html. If terms.html is missing, hugo will use list.html. But if taxonomy.html is missing, it won’t default to single.html.
https://github.com/guayom/hugo-taxonomies
Configure Taxonomies
Default without editing hugo.json:
{
"taxonomies": {
"category": "categories",
"tag": "tags"
}
}
You need to provide both the plural and singular labels for each taxonomy.
So hugo.json edited to create artists and venues taxonomies:
{
"baseURL": "http://localhost:1313/",
"languageCode": "en-us",
"title": "Seatavern v2",
"buildFuture": true,
"enableRobotsTXT": true,
"markup": {
"defaultMarkdownHandler": "goldmark",
"renderer": {
"unsafe": true
}
},
"taxonomies": {
"venue": "venues",
"artist": "artists"
}
}
Taxonomies are lists accessed by .Site.Taxonomies
{{ range $key, $taxonomy := .Site.Taxonomies.featured }}
...
{{{ end }}}
The landing page for a taxonomy is /tags/index.html
, similar to a section /section/index.html
Page resources
Content
{{ with .Resources.GetMatch "schema.json" }}
<<script type="application/ld+json">{{ .Content | safeJS }}</script>
{{ end }}
The content of the resource itself. For most resources, this returns a string with the contents of the file. Use this to create inline resources.
Data
Info from this discourse thread.
{{ $data := .Resources.GetMatch "schema.json" | transform.Unmarshal }}
It is documented at unmarshal a resource.
Frontmatter
It’s optional to include a resources
entry in the frontmatter.
{
"date": "2018-01-25",
"resources": [
{
"name": "schema",
"src": "images/schema.json"
}
]
}
The resource cant then be loaded from its name instead of path:
{{ with .Resources.GetMatch "schema" }}
<script type="application/ld+json">{{ .Content | safeJS }}</script>
{{ end }}
Configuration hugo.yaml
Dates
3 Content Types
1. Home
{{ .Page.IsHome }}
2. List
{{ .Page.IsSection }}
3. Single
{{ .Page.IsPage }}
Something frustrating for a newbie is figuring out which template is applied to what content:
jargon | template | content |
---|---|---|
home | layouts/index.html | ./_index.md |
——– | ——————– | ————- |
Front Matter
Hugo uses a number of key-value pairs, some of which can be set at the top of content files (eg foo.md), and others such as filename and section are read from disk.
---
title: A new post with the filename old-post.md
slug: "new-post"
---
hugo new site graph
cd graph
hugo new theme mytheme
tree ../graph
graph
├── archetypes
│ └── default.md
├── config.toml
├── content
├── data
├── layouts
├── resources
│ └── _gen
│ ├── assets
│ └── images
├── static
└── themes
└── mytheme
├── archetypes
│ └── default.md
├── layouts
│ ├── 404.html
│ ├── _default
│ │ ├── baseof.html
│ │ ├── list.html
│ │ └── single.html
│ ├── index.html
│ └── partials
│ ├── footer.html
│ ├── header.html
│ └── head.html
├── LICENSE
├── static
│ ├── css
│ └── js
└── theme.toml
Note index.html, _default/list.html and _default/single.html are all empty files that need to be created from scratch.
Templates
./themes/mytheme/layouts/_default/baseof.html
<!DOCTYPE html>
<html lang="en">
{{ partial "head.html" . }}
<body>
{{ partial "header.html" . }}
<div id="content">
{{ block "main" . }}
{{ end }}
</div>
{{ partial "footer.html" . }}
</body>
</html>
The above is a wrapper for blocks such as {{ block "main" . }}
and partials such as
themes/mytheme/layouts/_default/head.html
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .Title }}</title>
<link href="https://fonts.googleapis.com/css?family=Courier+Prime&display=swap" rel="stylesheet">
</head>
Page kinds
themes/mytheme/layouts/index.html
This looks for its content in ./content/_index.md
{{ define "main" }}
<main aria-role="main">
<header class="homepage-header">
<h1>{{.Title}}</h1>
{{ with .Params.subtitle }}
<span class="subtitle">{{.}}</span>
{{ end }}
</header>
<div class="homepage-content">
<!-- Note that the content for index.html, as a sort of list page, will pull from content/_index.md -->
{{.Content}}
</div>
<div>
{{ range first 10 .Site.RegularPages }}
{{ .Render "summary"}}
{{ end }}
</div>
</main>
{{ end }}
Useless Use of IsSet Award
I cluttered my partials with code like
{{- if isset . "addressCountry" }}
<meta itemprop="addressCountry" content="{{ .addressCountry }}" >
{{- end }}
before realising
This could be simplified to
{{- if .addressCountry }}
<meta itemprop="addressCountry" content="{{ .addressCountry }}" >
{{- end }}
or better yet
<meta itemprop="addressCountry" content="{{ .addressCountry }}" >