Alright, let’s talk about Branch Bundles. This is one of those Hugo concepts that seems bafflingly complex until the moment it clicks, and then you wonder how you ever lived without it. It’s the key to unlocking a clean, logical, and maintainable site structure. Forget about jamming everything into a posts folder; we’re building a proper content hierarchy here.

The core idea is simple: a Branch Bundle is just a content directory that represents a section of your site (like /blog or /projects) and can have its own content file, template, and metadata. The magic happens with the _index.md file. Think of it as the home page for that entire section.

The _index.md File: Your Section’s Home Base

Every folder you create in your content directory is a section. To give that section its own page—a list of its posts and some introductory content—you need an _index.md file. This isn’t a post; it’s the section’s landing page.

Its front matter controls how the section behaves. Here’s a typical example for a blog section:

---
title: "The Company Blog"
description: "Musings on technology, product updates, and our general shenanigans."
cascade:
    - _target:
          kind: page
      type: blog
      layout: single
---

Why _index.md and not index.md? Because index.html is already reserved for the final rendered output. Using _index.md is Hugo’s way of cleanly separating the content source from the rendered product. It’s a bit of a quirky naming convention, but it works.

The Section Template: How Your List Gets Built

When you visit /blog/, Hugo needs a template to render that section’s list page. It looks for a template in this order of specificity:

  1. layouts/section/blog.html (for the “blog” section specifically)
  2. layouts/section/list.html (a generic template for any section)
  3. layouts/_default/section.html (the final fallback)

Here’s a simple layouts/section/blog.html to get you started. The key is ranging through .Pages (or .RegularPages to exclude other _index.md files).

{{ define "main" }}
<article class="section-page">
    <header>
        <h1>{{ .Title }}</h1>
        <p>{{ .Description }}</p>
    </header>

    <!-- This renders the content from your blog/_index.md file -->
    <div class="content">
        {{ .Content }}
    </div>

    <ul class="post-list">
        <!-- .Pages here gives you all the regular pages in the blog section -->
        {{ range .Paginator.Pages }}
            <li>
                <a href="{{ .Permalink }}">{{ .Title }}</a>
                <span class="date">{{ .Date | time.Format ":date_long" }}</span>
            </li>
        {{ end }}
    </ul>

    <!-- Pagination is crucial for large sections -->
    {{ template "_internal/pagination.html" . }}
</article>
{{ end }}

The URL Structure: It’s All About That Cascade

This is where most people’s brains short-circuit. The URL for a page is determined by its position in the content tree relative to the _index.md files.

  • content/blog/_index.md → rendered as /blog/index.html
  • content/blog/my-first-post.md → rendered as /blog/my-first-post/index.html

The beauty (and occasional headache) is the cascade front matter in your _index.md. The settings you define there—like type, layout, or any other variable—will be passed down to all pages within that section. This is how you avoid manually setting type: blog in every single blog post’s front matter. It’s inheritance, and it’s glorious for keeping things DRY.

Common Pitfalls and How to Avoid Them

  1. The Missing List: You create a content/blog/post1.md but /blog/ gives you a 404. You forgot the _index.md file! Hugo won’t generate a section list page without it. The section literally doesn’t exist as a page without its _index.md.

  2. The Overzealous Cascade: Be careful what you put in that cascade block. If you set draft: true in a section’s _index.md and cascade it, you will accidentally hide every post in that section. I’ve done it. You’ll do it. We all have the T-shirt.

  3. Pagination Panic: For large sections, you must use the .Paginator object in your template (as shown above). Using .Pages directly will load every single item on one page. For a blog with 500 posts, this is a fantastic way to bring your site to a grinding halt. The .Paginator is your friend.

The power of Branch Bundles is that they let you model your content the way you think about it. A /docs section with subsections for each product? A /portfolio with sections for different types of work? It’s all just folders and _index.md files. It’s Hugo’s way of getting out of your way and letting you structure your content like a sane person, not a database table.