smhk

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:

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:

my-post.md
+++
# ...
series = ["Hugo web development"]
+++

Create a series partial §

Simple §

Following is a fairly simple example:

layouts/partials/series.html
{{ $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 the series parameter from the frontmatter. This partial ignores all other items in the list (e.g. if your frontmatter contained series = ["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:

layouts/partials/series.html
{{ $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 }}

  1. Handling a post which is part of more than one series is an exercise left for the reader! ↩︎