Hugo: How to create a post series
All notes in this series:
- Automating hugo development with npm scripts
- normalize-scss with hugo
- Automatic image thumbnails in Hugo from static directory
- Escaping Hugo shortcodes within Hugo markdown
- Hugo tag and category pages
- Bind hugo to localhost for development
- Hugo 0.37 does not support h6 markdown heading
- Install Hugo testing distribution on Debian
- Hugo anchors next to headers
- Hugo: Migrating from Pygments to Chroma
- Hugo: Global resources
- Hugo: How to create a post series
TL;DR: Follow these steps to implement a post series in Hugo, just like the one at the top of this page!
A few years back I followed this blog post to implement support for post series in Hugo. However, when I upgraded to Hugo 0.136.2 recently, this broke, and the series was just blank. I found this more recent blog post helpful in coming up with the following solution.
Define the series taxonomy §
Ensure a series named series
is defined in your hugo.toml
:
[taxonomies]
series = "series"
Populate the series taxonomy §
In the frontmatter for all the relevant posts, you can now define the series
parameter as a list. Simply use the same name in other posts to join them together as part of the same series:
+++
# ...
series = ["Hugo web development"]
+++
Create a series partial §
Simple §
Following is a fairly simple example:
{{ $series := slice }}
{{ if isset .Params "series" }}
{{ $postMainSeries := (index .Params.series 0) }}
{{ $series = (where .Site.RegularPages "Params.series" "intersect" (slice $postMainSeries)).ByDate }}
{{ $seriesLen := $series | len }}
{{ if gt $seriesLen 1 }}
<div class="series-container">
<h4>All notes in this series:</h4>
<ul class="series-list">
{{- range $post := $series -}}
<li><a href="{{$post.RelPermalink}}">{{ $post.Params.title }}</a></li>
{{ end }}
</ul>
</div>
{{ end }}
{{ end }}
The key points are:
$postMainSeries
: Stores the first item in theseries
parameter from the frontmatter. This partial ignores all other items in the list (e.g. if your frontmatter containedseries = ["Series 1", "Series 2", "Series 3"]
, this would pick"Series 1"
).1$series
: The pages in the series$postMainSeries
.$seriesLen
: Stores how many posts are in the series. If there is only one, then there is no point in displaying the series.{{- range $post := $series -}}
loops through all the posts in the series.
Advanced §
Going a step further, you can:
- Use the content type (e.g. note, hike, etc) to inform the design.
- Render the current post in the series differently (e.g. give it the
active
class, and remove the anchor tag).
For example:
{{ $series := slice }}
{{ if isset .Params "series" }}
{{ $postMainSeries := (index .Params.series 0) }}
{{ $series = (where .Site.RegularPages "Params.series" "intersect" (slice $postMainSeries)).ByDate }}
{{ $seriesLen := $series | len }}
{{ if gt $seriesLen 1 }}
<div class="series-container">
{{ if eq .Type "note" }}
<h4>All notes in this series:</h4>
{{ else if eq .Type "hike" }}
<h4>All hikes at this location:</h4>
{{ else }}
<h4>Everything in this series:</h4>
{{ end }}
<ul class="series-list">
{{- range $post := $series -}}
{{ if eq $post.Permalink $.Page.Permalink }}
<li class="active">{ $post.Params.title }}</li>
{{ else }}
<li><a href="{{$post.RelPermalink}}">{{ $post.Params.title }}</a></li>
{{ end }}
{{ end }}
</ul>
</div>
{{ end }}
{{ end }}
Handling a post which is part of more than one series is an exercise left for the reader! ↩︎