Adding categories and tags index pages to hugo-apéro

By Jack Gregory in Web Development

August 5, 2026

TL;DR

In this blog, I summarize the steps necessary to add index pages for categories and tags on a Hugo-Apéro website — an alphabetical, described list of categories, and a grouped, alphabetised list of tags — while keeping the theme’s default term pages intact.

Introduction

Hugo-Apéro’s default taxonomy setup generates a page for every individual term — /categories/web-development/, /tags/hugo/, and so on — listing the posts that carry it. What it doesn’t generate is a landing page at /categories/ or /tags/ that summarizes all terms at once. For a small site this is barely noticeable, but once a category or tag vocabulary grows past a handful of terms, a visitor has no way to browse what’s available without already knowing a term’s URL.

In this blog, I’ll outline the five steps necessary to add category and tag index pages to your own Hugo-Apéro website:

  1. Store category descriptions and tag groupings as data;
  2. Build the categories index page;
  3. Build the tags index page;
  4. Keep the default term pages; and,
  5. Make the index pages accessible.

Store category descriptions and tag groupings as data

Hugo already knows which categories and tags exist and how many posts use each — that comes from .Site.Taxonomies. What it has no way to know is a one-sentence description of a category, or which conceptual group a tag belongs to. That’s editorial judgment, and it needs to live somewhere as data rather than being repeated in every post’s front matter.

To do ✔️

  • Category data: Create data/categories.yaml at the site root (not inside the theme folder), with one entry per category.
  • Tag data: Create data/tags.yaml alongside it, grouping tags under headings.

Code 💻

# data/categories.yaml
- slug: data-science
  name: "Data Science"
  description: "Applied methods for turning messy datasets into usable analysis."
- slug: web-development
  name: "Web Development"
  description: "Practical, step-by-step notes on building and maintaining this site."
# data/tags.yaml
- group: "Tools & Languages"
  terms: ["R", "Hugo", "Go", "YAML", "CSS"]
- group: "Format & Context"
  terms: ["Tutorial", "Working Paper"]

Files under data/ are parsed by Hugo and exposed to templates via site.Data — unlike static/, which just copies files through untouched. That’s what makes site.Data.categories and site.Data.tags usable inside the templates below.

Build the categories index page

The categories index needs an alphabetical list of every term Hugo knows about, each matched against its description from the data file.

To do ✔️

  • Categories index template: Create layouts/categories/taxonomy.html.

File 📄

{{ define "main" }}
<main class="page-main pa4" role="main">
  <section class="page-content mw7 center">
    <article class="post-content pa0 ph4-l">
      <header class="post-header">
        <h1 class="f1 lh-solid measure-narrow mb3 fw4">Categories</h1>
      </header>
      <section class="post-body pt5 pb4">
        <ul class="list pl0">
          {{ range .Data.Terms.Alphabetical }}
            {{ $slug := .Name | urlize }}
            {{ $entry := index (where site.Data.categories "slug" $slug) 0 }}
            <li class="mv4">
              <a href="{{ .Page.RelPermalink }}" class="f4 b link dim">
                {{ with $entry }}{{ .name }}{{ else }}{{ .Name | humanize }}{{ end }}
              </a>
              <span class="f6 gray"> ({{ .Count }})</span>
              <p class="f6 measure lh-copy mv1">
                {{ with $entry }}{{ .description }}{{ else }}<em>No description yet — add one to data/categories.yaml.</em>{{ end }}
              </p>
            </li>
          {{ end }}
        </ul>
      </section>
    </article>
  </section>
</main>
{{ end }}

.Data.Terms.Alphabetical is Hugo’s built-in accessor for every term actually in use on a taxonomy page, sorted, complete with each term’s post .Count and link — no manual list to maintain. The where site.Data.categories "slug" $slug lookup is the only manual step, matching the live term against its entry in the data file. A category used in front matter but missing from data/categories.yaml still renders, just with a placeholder prompting a description to be added, rather than silently disappearing.

Build the tags index page

Tags follow the same live-lookup pattern, but grouped under the headings from data/tags.yaml rather than listed flat, and sorted alphabetically within each group.

To do ✔️

  • Tags index template: create layouts/tags/taxonomy.html.

File 📄

{{ define "main" }}
<main class="page-main pa4" role="main">
  <section class="page-content mw7 center">
    <article class="post-content pa0 ph4-l">
      <header class="post-header">
        <h1 class="f1 lh-solid measure-narrow mb3 fw4">Tags</h1>
      </header>
      <section class="post-body pt5 pb4">
        {{ range site.Data.tags }}
          <h2 class="f7 fw5 mt5 mb3">{{ .group }}</h2>
          <ul class="list pl0 flex flex-wrap">
            {{ range sort .terms }}
              {{ $slug := . | urlize }}
              {{ $count := 0 }}
              {{ with index site.Taxonomies.tags $slug }}{{ $count = len . }}{{ end }}
              <li class="mr3 mb3">
                <a href="{{ "tags/" | absURL }}{{ $slug }}" class="f6 link dim{{ if eq $count 0 }} o-50{{ end }}">{{ . }}</a>
                {{ if gt $count 0 }}<span class="f7 gray"> ({{ $count }})</span>{{ end }}
              </li>
            {{ end }}
          </ul>
        {{ end }}
      </section>
    </article>
  </section>
</main>
{{ end }}

Because grouping is editorial rather than something Hugo tracks, this template loops over site.Data.tags directly instead of .Site.Taxonomies.tags. Each tag is still checked against the live taxonomy to show a post count where it’s actually in use, and any tag that’s been planned in the data file but not yet used on a post fades out via o-50 rather than linking to an empty page.

Keep the default term pages

Adding a taxonomy.html template only replaces the index page at /categories/ and /tags/ — but Hugo resolves term pages like /categories/web-development/ using a different template. Apéro’s own layouts/taxonomy/term.html no longer gets picked up once layouts/categories/ and layouts/tags/ exist as override folders, so without a matching term.html inside each, those individual term pages stop being generated entirely.

To do ✔️

  • Term templates: copy the theme’s layouts/taxonomy/term.html into both override folders, unchanged.

Code 💻

cp themes/hugo-apero/layouts/taxonomy/term.html layouts/categories/term.html
cp themes/hugo-apero/layouts/taxonomy/term.html layouts/tags/term.html

With that in place, layouts/categories/ and layouts/tags/ each carry two templates — taxonomy.html for the summary index, term.html for individual term pages — and both behave exactly as before.

Make the index pages accessible

Building the templates makes /categories/ and /tags/ functional, but nothing on the site linked to them yet. I added links in two places: the site footer, so they’re reachable from anywhere, and the blog sidebar, so they’re reachable in context while browsing posts.

For the former, Apéro already renders the contents of menu.footer at the bottom of every page, so adding the two entries was enough on its own — no template change needed. Use the weight parameter to position the links as preferred.

To do ✔️

  • Footer links: Add Categories and Tags entries to the footer menu in config.toml.

Code 💻

[[menu.footer]]
  name    = "Categories"
  title   = "Categories"
  url     = "/categories/"
  weight  = 1
[[menu.footer]]
  name    = "Tags"
  title   = "Tags"
  url     = "/tags/"
  weight  = 2

For the latter, it is simply necessary to activate the appropriate fields in the blog/_index.md file. Unlike the footer entries, this link only appears in the sidebar while browsing the blog section, giving readers a second, more contextual way to reach the same two pages.

To do ✔️

  • Blog sidebar links: In content/blog/_index.md, switch on the sidebar’s existing categories_link and tags_link parameters.

Code 💻

sidebar:
  categories_link:      true
  tags_link:            true

Usage

With the data files, index templates, term templates, and links all in place, maintaining the taxonomy going forward is mostly editorial:

  • Add a new category or tag to a post’s front matter as usual — it appears on the relevant term page automatically.
  • Add a matching entry to data/categories.yaml (with a description) or data/tags.yaml (under the right group) so it’s represented properly on the index pages, rather than falling back to the “no description yet” placeholder.

References

I found the following references helpful in adding these taxonomy index pages as well as preparing this blog: