Emacs, scripting and anything text oriented.

Hugo: Leaf and Branch Bundles

Kaushal Modi

Content organization for Hugo posts/articles using Page Bundles.

Fix the term and taxonomy page Kind references updated as of Hugo v0.73.0.
Use the Hugo v0.53 introduced site var in examples.
Clarify the leaf vs branch decision making section.

Hugo v0.32 introduced a new feature called Page Bundles (1, 2), as a way to organize the content files. It is useful for cases where a page or section’s content needs to be split into multiple content pages for convenience, or has associated attachments like documents or images.

A Page Bundle can be one of two types:

  • Leaf 🍃 Bundle
  • Branch 🌿 Bundle

The leaf and branch nodes from Data Structure Trees serve as a great analogy to help relate with the Hugo bundles.

A node with no children.
A node with at least one child.

Quick Summary #

Here’s a quick summary of the differences between these bundle types — Details follow in the sections after.

Table 1: Leaf Bundle vs Branch Bundle
Leaf BundleBranch Bundle
UsageCollection of content and attachments for single pagesCollection of attachments for section pages (home page, sections, taxonomy list pages, term list pages)
Index file nameindex.md 1_index.md 1
Allowed ResourcesPage and non-page (like images, pdf, etc) typesOnly non-page (like images, pdf, etc.) types
Where can the Resources live?At any directory level within the leaf bundle directory.Only in the directory level of the branch bundle directory i.e. the directory containing the _index.md (ref).
Layout typesinglelist
NestingDoes not allow nesting of more bundles under itAllows nesting of leaf or branch bundles under it
Content from non-index page files ..Accessed only as page resourcesAccessed only as regular pages

Hugo v0.73.0 made a much needed fix — It finally renamed taxonomy Page Kind to term, and taxonomyTerm to taxonomy. Now if you have a tags = ["abc"] front-matter, tags is the taxonomy and abc is a term.

Leaf Bundle #

A Leaf Bundle is a directory at any hierarchy within the content/ directory, that contains an index.md file.

Examples of Leaf Bundle organization #

├── about
│   └── index.md
├── posts
│   ├── my-post
│   │   ├── content1.md
│   │   ├── content2.md
│   │   ├── image1.jpg
│   │   ├── image2.png
│   │   └── index.md
│   └── my-another-post
│       └── index.md
└── another-section
    ├── ..
    └── not-a-leaf-bundle
        ├── ..
        └── another-leaf-bundle
            └── index.md

In the above example content/ directory, there are four leaf bundles:

This leaf bundle is at the root level (directly under content directory) and has only the index.md.
This leaf bundle has the index.md, two other content Markdown files and two image files. The content from the two non-index .md files can be accessed only as page resources, not as regular files i.e. those non-index .md files will not have their own permalinks or HTML files in public/.
This leaf bundle has only the index.md.
This leaf bundle is nested under couple of directories. This bundle also has only the index.md.

The hierarchy depth at which a leaf bundle is created does not matter, as long as it is not inside another leaf bundle.

Headless Bundle #

A headless bundle is a bundle that is configured to not get published anywhere:

  • It will have no Permalink and no rendered HTML in public/.
  • It will not be part of site.RegularPages, etc.

But you can get it by site.GetPage. Here is an example:

{{ $headless := site.GetPage "page" "some-headless-bundle" }}
{{ $reusablePages := $headless.Resources.Match "author*" }}
{{ range $reusablePages }}
    <h3>{{ .Title }}</h3>
    {{ .Content }}
{{ end }}

A leaf bundle can be made headless by adding below in the Front Matter (in the index.md):

headless = true

Only leaf bundles can be made headless.

Example uses of headless page bundles:

  • Shared media galleries
  • Reusable page content “snippets”

Branch Bundle #

A Branch Bundle is a directory at any hierarchy within the content/ directory, that contains an _index.md file. This _index.md can also be directly under the content/ directory, to set the Home page content and/or front-matter variables.

By default, Hugo considers the first directory level under content/ as a section. But a branch bundle can be used to create a section at any hierarchy.

Examples of Branch Bundle organization #

├── _index.md
├── branch-bundle-2
│   ├── branch-content1.md
│   ├── branch-content2.md
│   ├── image1.jpg
│   ├── image2.png
│   └── _index.md
└── branch-bundle-3
    ├── _index.md
    └── a-leaf-bundle
        └── index.md

In the above example content/ directory, there are three branch bundles (and a leaf bundle):

Home page as branch bundle
The home page content is organized as a branch bundle, using _index.md directly in the content/ directory. It consists of two other branch bundles.
This branch bundle has the _index.md, two other content Markdown files and two image files. The content from the two non-index .md files can be accessed only as regular pages, not as page resources. Branch bundles cannot have page resources, so any non-index Hugo recognized content file in a branch bundle can be accessed like any regular page (just like in any section.. as.. a branch bundle is a section too).
This branch bundle has the _index.md and a nested leaf bundle.

The hierarchy depth at which a branch bundle is created does not matter.

Branch Bundle vs Regular Sections #

Branch Bundle section
Any directory in content/ that contains the _index.md file.
Regular Section
Any first-level directory in content/, and also home page and taxonomy list page .. that is not already a Branch Bundle.

Table 2: Branch Bundle section vs Regular section
Branch Bundle SectionRegular Section
Layout typelistlist
Can contain non-page Resources?YesNo
Can contain page Resources?NoNo

Branch Bundle is basically a Section with non-page Resources.

Here’s an example snippet that can be used in a list template to distinguish between the two:

{{ if (eq .Kind "section") }}                          <!-- Section -->
    {{ if (eq .File.Path "") }} <!-- Regular section -->
        <h1>Posts in ‘{{ .Dir | default .Section }}</h1>
    {{ else }}                  <!-- Branch bundle section -->
        <h1>{{ .Title }}</h1>
        <h2>Posts in ‘{{ .Dir | default .Section }}</h2>
    {{ end }}
{{ end }}

So, which bundle should I use? #

In summary:

  • If it’s a regular page or a single page where you write your content yourself, create a leaf bundle.

    Page Bundles of page Kind are always leaf bundles.. and vice versa.

  • Otherwise, if it’s a list page which Hugo generates for you, create a branch bundle. Such pages typically are a list of regular pages or even other list pages. Pages of home, section, taxonomy and term Kind are always branch bundles.

Figure 1: Do I need a leaf bundle, or branch bundle, or none?

Figure 1: Do I need a leaf bundle, or branch bundle, or none?

Upgrading to Hugo v0.32? #

All of this boils down to these few caution points if you are upgrading Hugo from a pre-0.32 version to 0.32 or a newer version:

  • If a directory foo has an index.md (leaf bundle), that file will be the content file for that foo Regular Page.
  • If a directory foo has an _index.md (branch bundle), that file will be the content file for that foo Section Page, and all other2 .md files in the same or deeper hierarchy levels under foo/ will become Regular Pages under that foo section.

Examples #

Of course, a post like this is not complete without examples, so here they are:

Table 3: Leaf and Branch bundle examples
ExampleMarkdown sourceHTML output
This page (leaf bundle)content/posts/hugo-leaf-and-branch-bundles/duh!
Leaf and branch bundle examples from ox-hugo test sitecontent/bundles/Bundles
Headless (leaf) bundle example from the samecontent page using headless bundle + Layout fetching headless bundle pagesPage using the Headless Page Bundle
Branch bundles used for home, taxonomy, and term Kind pagesHugo Sandbox content/ dirHugo Sandbox

Further Reading #

I suggest reading the below pages in the Hugo documentation, as they complement the Page Bundles concept:

  1. The .md extension for index.md, _index.md, and all other content files in this post is just an example. The extension can be .html or any of any valid MIME type recognized by Hugo. ↩︎ ↩︎

  2. It’s a qualified “all other” — That does not count the content files further nested in leaf and branch bundles in that foo section. ↩︎