Alright, let’s talk about .Site.RegularPages.Related. This is Hugo’s built-in attempt at being a mind reader, and frankly, it’s a bit of a party trick. It’s designed to give you a list of pages that are, well, related to the current one. Sounds magical, right? It can be, but like any good magic trick, it’s all in the setup, and if you do it wrong, you’ll just pull a rabbit with two heads out of your hat.

The core thing you need to understand is how Hugo decides two pages are related. It’s not AI; it’s not sentient. It uses a very simple, configurable algorithm based on front matter parameters. By default, it gets religiously obsessed with tags. If two pages share a common tag, Hugo considers them kin. You can also configure it to care about categories or even other fields you define.

How It Works (The Voodoo Inside)

Under the hood, Hugo creates something called a “search index” for your entire site. When you call .Related on a page, it essentially runs a tiny search query against that index, looking for other pages with the most overlapping keywords (from your configured front matter fields). The page with the most matches “wins” and is considered the most related.

The number of related pages returned is capped at the value you set in your site config with related: limit:. The default is a paltry 5, which is often about 3 too few.

# config.yaml
related:
  limit: 6 # A slightly more reasonable default
  indices:
    - name: tags
      weight: 100 # The importance of a match in this field
    - name: categories
      weight: 50

Why weight? Because you might want a tag match to be twice as significant as a category match in determining relatedness. A page that shares a tag and a category will rocket to the top of the list.

The Basic, No-Frills Implementation

Here’s the absolute minimum code to slap this into a single template, probably your layouts/_default/single.html.

{{ $related := .Site.RegularPages.Related . }}
{{ with $related }}
  <aside>
    <h3>See Also (Because Hugo Says So)</h3>
    <ul>
      {{ range . }}
        <li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
      {{ end }}
    </ul>
  </aside>
{{ end }}

We use .Site.RegularPages.Related . – we’re asking the entire site’s collection of regular pages (so not sections, taxonomies, etc.) for those related to the current page (the dot .). The with block is a nice, safe way to only output the HTML if there are actually related pages to show.

Common Pitfalls and the “Why Is This Empty?!” Dilemma

You will write this code, refresh your page, and see nothing. It happens to the best of us. Here’s your debugging checklist:

  1. Not Enough Matches: The default limit is 5, but the default threshold for being included at all is 1 shared keyword. If you have fewer than 5 pages that share any tags/categories, you’ll get fewer than 5 back. This is often the case in smaller sites. Check your limit config.
  2. No Shared Taxonomy: This is the big one. Your current page has tags: ["Hugo", "WebDev"]. Another page has tags: ["Cooking", "Sourdough"]. Hugo looks at this and, correctly, sees no relation. They live in different universes. You need a common term.
  3. You’re Using a Custom Field and Forgot to Tell Hugo: Say you want to use a custom front matter field like keywords. You must declare this in your config, or Hugo will blissfully ignore it.
# config.yaml
related:
  indices:
    - name: keywords # Now Hugo will care about this field too
      weight: 80
  1. You’re on the Homepage or a List Page: .Related is a method on Page. It only makes sense in the context of a single page. Calling it from a list template (like index.html) where . is a collection of pages will fail spectacularly and silently.

Best Practices and Leveling Up

The basic implementation is… fine. But we can do better. The default algorithm is a blunt instrument. Let’s sharpen it.

First, always, always set a sensible default in your config. Relying on the default of 5 is a recipe for a UI that looks broken.

Second, consider using the more powerful .RelatedTo method. While .Related gives you pages related to the current one, .RelatedTo lets you query for pages related to a specific set of keywords. It’s a different way to slice the problem and can be useful for creating custom “related” sections based on a specific tag.

Third, and this is the pro move, don’t just rely on Hugo’s algorithm. Its logic is simplistic. Use it to get a candidate list, then filter it further yourself.

{{ $related := .Site.RegularPages.Related . | first 4 }}
{{ with $related }}
  <aside>
    <h3>You Might Actually Like This</h3>
    <div class="grid grid-cols-2 gap-4">
      {{ range . }}
        <article class="border p-4">
          <h4 class="text-lg font-semibold"><a href="{{ .RelPermalink }}">{{ .Title }}</a></h4>
          {{ with .Description }}<p class="text-sm mt-2">{{ . }}</p>{{ end }}
        </article>
      {{ end }}
    </div>
  </aside>
{{ else }}
  {{/* Fallback to *something* if there are no related pages */}}
  {{ $fallback := where site.RegularPages "Type" "blog" | first 4 }}
  {{ with $fallback }}
    <aside>
      <h3>Latest from the Blog</h3>
      ...
    </aside>
  {{ end }}
{{ end }}

See what we did there? We used | first 4 to get a predictable number of items for our layout. And most importantly, we added an else block with a fallback. If Hugo’s voodoo fails to find any related pages, we don’t just leave a blank space on the page – we pull in the latest posts instead. This is the difference between a website that looks empty and one that always has valuable content to offer. It’s not just a technical implementation; it’s a user experience consideration. And that, my friend, is how you use this feature correctly.