Right, let’s talk about the beating heart of your Hugo site’s content engine: layouts/_default/list.html. This isn’t just a template; it’s the template Hugo reaches for by default when it can’t find a more specific one. It’s the workhorse. It’s the one you’ll be wrestling with the most, so we’d better get to know it intimately.

Think of it this way: any time you ask Hugo to show you a group of something—all your blog posts, all the projects in your “Web Development” category, all the items tagged “golang”—it’s going to try to use this template. Its job is to take a list of pages (the .Pages or .RegularPages variable) and render them. The logic is simple: range through the pages and do something with each one. But the devil, as always, is in the details.

The Core Anatomy of a List Template

At its absolute simplest, the template is a range loop. But if you ship a site with just this, you and I are going to have words. It’s functional, but it’s a bare skeleton.

{{ define "main" }}
  <h1>{{ .Title }}</h1>
  {{ .Content }}
  {{ range .Pages }}
    <article>
      <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
      <time>{{ .Date.Format "January 2, 2006" }}</time>
      <p>{{ .Summary }}</p>
    </article>
  {{ end }}
{{ end }}

Let’s break down the moving parts. Inside the {{ define "main" }} block, you’re in the context of the current list page (e.g., the blog section itself). Its .Title is “Posts”, its .Content is any front matter body you wrote for that section’s _index.md file. When you {{ range .Pages }}, you’re shifting context to each individual page within that list. Now .Title refers to the blog post’s title, .Date to its date, and so on. This context shift is the number one thing that trips people up. You’re not crazy; it’s just a bit awkward.

Why .RegularPages is Usually What You Want

You might see .Pages and .RegularPages and wonder what the difference is. Here’s the deal: .Pages includes every kind of page in the section, including any other non-regular pages like your section’s _index.md file itself. This is almost never what you want in your list loop—imagine listing the “Blog” page inside the list of blog posts. It’s weird.

.RegularPages filters out those non-regular pages, giving you only the content you actually wrote articles for. It’s the clean, sane choice for your listing loops 99% of the time.

{{ range .RegularPages }}
  <!-- This will only loop through your actual blog posts, projects, etc. -->
{{ end }}

Pagination: Because Nobody Wants a 10,000-Page Scroll

If you have more than a handful of items, you need pagination. It’s not optional. Hugo has first-class support for it, and it’s criminal not to use it. The magic lies in using the .Paginate function instead of a direct range. This one change splits your .RegularPages into manageable chunks.

{{ define "main" }}
  <h1>{{ .Title }}</h1>
  {{ .Content }}
  {{ $paginator := .Paginate .RegularPages }}
  {{ range $paginator.Pages }}
    <article>... </article>
  {{ end }}
  <!-- This next part is crucial -->
  {{ template "_internal/pagination.html" . }}
{{ end }}

The {{ template "_internal/pagination.html" . }} line is genius. It pulls in Hugo’s built-in, perfectly functional pagination partial. Use it. Don’t waste an afternoon building your own pagination controls from scratch right now. Get the site working, then come back and style that internal partial later if you must.

The “No Content” Edge Case

What happens if you create a “Reviews” section but haven’t written any reviews yet? Your template will render… nothing. It’s a ghost town. This is bad UX. Always, always plan for the empty state.

{{ $paginator := .Paginate .RegularPages }}
{{ if $paginator.Pages }}
  {{ range $paginator.Pages }}
    <article>... </article>
  {{ end }}
  {{ template "_internal/pagination.html" . }}
{{ else }}
  <p>Well, this is awkward. Nothing here yet. Check back later!</p>
{{ end }}

The Home Page is Just Another List

Here’s the kicker that makes everything click: your home page (/) is, technically, a list page. It’s a list of something, usually your site’s regular pages. Its template is typically found at layouts/index.html. But if that file doesn’t exist, Hugo will fall back to using our old friend _default/list.html. This is why understanding this template is so powerful—it controls more than you think.

Often, you want your home page to be special. So you create layouts/index.html and give it a different layout—maybe a hero section, some featured content, and then a truncated list of recent posts. But at its core, you’re still using the same concepts: .Site.RegularPages or .Paginate .Site.RegularPages.

{{/* layouts/index.html */}}
{{ define "main" }}
  <section class="hero">...</section>
  <h2>Recent Ramblings</h2>
  {{ $pages := where site.RegularPages "Type" "in" site.Params.mainSections }}
  {{ range first 5 $pages }}
    <article>... </article>
  {{ end }}
{{ end }}

The key here is where site.RegularPages "Type" "in" site.Params.mainSections. This is the canonical way to grab pages from your primary sections (like “posts” and “projects”) without hardcoding them, making your theme portable. You define the mainSections list in your site’s config. It’s a small touch that separates a hacky setup from a professional one. Always code for the next person who has to use your theme—even if that person is future you.