Alright, let’s get our hands dirty with .Pages and .RegularPages. If you’re here, you’ve probably realized that Hugo’s idea of a “page” is a bit more expansive than yours. You think of a page as, well, a page. Hugo sees a page as any content-bearing node in its site structure. This distinction is the entire reason these two variables exist, and understanding it will save you from a world of head-scratching moments.

Think of your content tree. You have your regular pages—posts/my-great-post.md, projects/hugo-guide.md—the actual meat of your site. But you also have sections like posts/ or projects/ themselves. In Hugo’s world, those sections are also pages. They have their own content files (e.g., _index.md) and they act as parents to their children. This is a powerful concept, but it can be a real nuisance when you’re just trying to list the blog posts someone came to read.

That’s where our dynamic duo comes in.

The Core Difference: Pages vs. Regular Pages

  • .Pages: This is the all-inclusive, “bring the whole family” list. It includes every kind of child page: your regular pages (the good stuff) AND any other section pages (the sometimes-annoying stuff that can clutter up a list). If you have a blog section, .Pages would list both your individual blog posts and any sub-sections like blog/2024/.
  • .RegularPages: This is the bouncer at the exclusive club. It only lets in the regular, leaf-level pages. It filters out all those section pages, leaving you with a clean list of just the content your visitors are actually looking for. Ninety-nine percent of the time, especially for things like blogrolls or project lists, this is the variable you want.

Here’s a classic scenario. You have this structure:

content/
└── blog/
    ├── _index.md
    ├── first-post.md
    ├── second-post.md
    └── deep-dives/
        ├── _index.md
        └── a-deep-dive.md

In your blog/list.html template, you’d see this behavior:

<!-- This will include 4 items: first-post, second-post, the 'deep-dives' section, and a-deep-dive -->
<ul>
{{ range .Pages }}
  <li>{{ .Title }} ({{ .Kind }})</li>
{{ end }}
</ul>

<!-- This will include ONLY the 3 regular pages: first-post, second-post, and a-deep-dive -->
<ul>
{{ range .RegularPages }}
  <li>{{ .Title }} ({{ .Kind }})</li>
{{ end }}
</ul>

The output would clearly show the difference. The first list would be a mess of posts and a section, while the second would be a clean, predictable list. You almost always want the second one.

The Homepage Is a Weirdo (And .RegularPagesRecursive Is Your Friend)

Here’s the first “questionable choice” we need to talk about. The homepage (.Site.Home) is a section like any other, but its behavior is… special. For some reason known only to the Hugo core team, calling .RegularPages on the homepage does not recursively get all regular pages on your entire site. It only gets the direct, regular page children of the homepage. This is almost never what anyone wants. It’s a bizarre and frustrating default.

What you actually want is a list of all regular pages across the entire site, sorted by date or weight. For that, you need to reach for the global site variable and its friend:

<!-- On the homepage, this is likely useless and will only show pages in /content/ -->
{{ range .Site.Home.RegularPages }}
  <li>{{ .Title }}</li>
{{ end }}

<!-- This is what you actually want: ALL regular pages, site-wide -->
{{ range .Site.RegularPages }}
  <li>{{ .Title }}</li>
{{ end }}

<!-- Or, if you need the recursive behavior from a specific section context, use: -->
{{ range .Pages.Recursive.RegularPages }}
  <li>{{ .Title }}</li>
{{ end }}

The fact that .Site.RegularPages even has to exist is a tacit admission that .RegularPages on the homepage isn’t sufficient. It’s a workaround for a design quirk. Remember it; you’ll need it.

Ordering and Pagination: They Work the Same

The good news is that both variables are just collections of pages, so all the standard methods for sorting and filtering work identically on both.

<!-- Sort your regular pages by date, then title -->
{{ $paginator := .Paginate (.RegularPages.ByDate.Reverse) }}
{{ range $paginator.Pages }}
  <h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
{{ end }}
{{ template "_internal/pagination.html" . }}

You can use .ByPublishDate, .ByTitle, .ByParam "my_param", or any other sorting method. Pagination works exactly the same way, whether you’re paginating .Pages or .RegularPages. The only difference is what you’re paginating.

Best Practices and Pitfalls

  1. Default to .RegularPages: Unless you have a specific, conscious need to list sub-sections (like building a sitemap of your content structure), always reach for .RegularPages first. It does what you expect.
  2. The Homepage Trap: Never forget the homepage’s odd behavior. If you’re working on layouts/index.html and want to list content, you almost certainly want .Site.RegularPages, not .RegularPages.
  3. Performance is Identical: There’s no performance penalty for using one over the other. Hugo builds both lists as it constructs the site graph; it’s just a matter of which nodes it includes in the final slice.
  4. .Pages for Section Navigation: The one place .Pages shines is when you are intentionally building a navigational structure for a section. For example, in a docs section, you might want to list both the sub-sections (e.g., “Tutorials”, “Reference”) and the individual pages within the current section.

Mastering this distinction is a rite of passage. It’s the moment you stop fighting Hugo’s content model and start using it to your advantage. Now go build something that doesn’t list its own folders.