12.3 Lookup for List Pages: Sections, Taxonomies, and Home
Right, so you’ve told Hugo to build a list of something—your blog posts, your tags, your collection of vintage spoons. It knows what content you want. Now it needs to figure out how to dress it up. This is the template lookup, and for list pages, it’s a surprisingly robust (some might say convoluted) cascade of fallbacks. The logic is basically Hugo saying, “Okay, do they have a super specific template for this exact thing? No? Well, what about the generally specific one? Still no? Fine, we’ll just use the default. Everyone loves a default.”
The lookup order is a path of decreasing specificity, and it’s your best friend for keeping your template directory clean. You don’t need to create a template for every single scenario, just the ones where you want to break from the norm.
The Lookup Order for Section Lists
Let’s say you have a section called blog. You visit yoursite.com/blog/. Hugo needs a template to render that list of articles. It’s going to look for templates in this order, and use the first one it finds:
layouts/section/blog.htmllayouts/section/list.htmllayouts/_default/section.htmllayouts/_default/list.html
See the pattern? It goes from the specific (blog.html) to the general (list.html). The section/ directory is the special place for section lists. The _default/ directory is the catch-all bucket for everything that doesn’t have a more specific home. This is where 90% of your customization will happen. You’ll almost always create a layouts/_default/list.html as your base list template, and then maybe a layouts/section/news.html when your “News” section needs to look radically different from everything else.
The Homepage is Just a Special Section
I love this part because it’s so sensible. Your homepage? It’s just the list of content for the root section of your site. Its lookup order is almost identical, just with index swapped in:
layouts/index.htmllayouts/_default/list.htmllayouts/_default/home.html
Yes, it checks for home.html last. I know, it feels backwards. The designers presumably chose this because index.html is the most explicit name for a homepage, and list.html is the logical fallback since the homepage is, functionally, a list. The home.html is a legacy-friendly alias. My advice? Just use layouts/index.html for your homepage and forget the others exist. It’s the clearest and most intentional choice.
How Taxonomy Pages Fit In
Taxonomies (like tags and categories) are also lists! Specifically, there are two types of lists for any taxonomy: the list of all terms (e.g., all tags) and the list of all content for a single term (e.g., all posts with the tag “golang”).
For a list of terms (e.g., /tags/), the lookup is:
layouts/taxonomy/tags.terms.html(I know, the.termspart is weird)layouts/taxonomy/list.htmllayouts/_default/taxonomy.htmllayouts/_default/terms.htmllayouts/_default/list.html
For a list of content within a term (e.g., /tags/golang/), it’s much cleaner:
layouts/taxonomy/tags.html(for the specific taxonomy)layouts/taxonomy/tag.html(wait, singular? Yes, also singular. Because why not.)layouts/taxonomy/list.htmllayouts/_default/taxonomy.htmllayouts/_default/list.html
The pitfall here is obvious: the naming is a mess. .terms.html, singular, plural—it’s a grammar lesson gone wrong. The best practice is to be consistent. I create layouts/_default/list.html as my base, and then if I need to style my tag pages specifically, I’ll create layouts/taxonomy/tag.html (singular) which will then override the default for all term content lists.
Here’s a real-world example. This is a bare-bones layouts/_default/list.html that will handle most of your lists:
<!DOCTYPE html>
<html>
<head>
<title>{{ .Title }} | My Site</title>
</head>
<body>
<h1>All {{ .Type | pluralize }}</h1>
{{ .Content }} <!-- Renders any _index.md content -->
<ul>
{{ range .Pages }}
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
{{ end }}
</ul>
</body>
</html>
And then, if I want my blog section to have a hero image, I create layouts/section/blog.html and add the extra flair there. Hugo will use that file instead of the default, and I don’t have to clutter my default template with a bunch of {{ if eq .Section "blog" }} statements. It’s a clean separation of concerns. The why is simple: it lets you override behavior in one place without touching the other, which is the hallmark of a good system.