How Hugo's Page Bundle System Organizes Static Assets with Content: A Complete Guide

Hugo's page bundle system treats directories containing index.md or _index.md as self-contained units that co-locate content files with their static assets, enabling resource-specific processing methods directly in templates without using the global static/ folder.

The gohugoio/hugo static site generator implements a sophisticated content organization architecture that binds static assets to their logical content owners. This system parses filesystem paths to classify resources, distinguishes between page types using specific filename conventions, and exposes powerful template methods for asset manipulation. Understanding how Hugo processes these bundles at the source code level allows developers to optimize site architecture and asset pipelines.

What Is a Hugo Page Bundle?

A page bundle is a directory within Hugo's content filesystem that contains both a content file (index.md or _index.md) and associated page resources such as images, PDFs, JSON files, or SVG diagrams. When Hugo parses the filesystem, it builds a paths.Path object in common/paths/pathparser.go to classify each file.

Files residing alongside the index file—or in subdirectories within the bundle—are marked as TypeContentResource via the ModifyPathBundleTypeResource function. This classification distinguishes page-specific assets from global static files, allowing Hugo to attach them directly to the Page object rather than treating them as generic site resources.

Leaf Bundles vs. Branch Bundles

Hugo recognizes two distinct bundle types based on the content filename, both defined in common/paths/pathparser.go:

  • Leaf bundles (TypeLeaf): Created when a directory contains index.md. These represent single content pages and cannot contain nested sections.
  • Branch bundles (TypeBranch): Created when a directory contains _index.md. These represent section list pages and can contain other pages or nested sections.

The bundle type is exposed through the PageMetaProvider.BundleType() method in resources/page/page.go, allowing templates to query the structural role of any given page. Leaf bundles are ideal for articles with associated media, while branch bundles organize section-level resources like category headers or landing page assets.

How Hugo Processes Page Resources

During site construction, Hugo walks the content tree and classifies resources using the path parser. The Resources collection—implemented in methods/page/Resources—attaches all detected files to their parent Page object. This collection provides lookup helpers including:

  • Get: Retrieves a resource by exact relative path
  • GetMatch: Retrieves a resource by pattern or metadata name
  • Match: Returns all resources matching a glob pattern
  • ByType: Filters resources by MIME type (e.g., image, application)

Each resource implements the resource.Resource interface, exposing methods like RelPermalink, Resize, Content, and Title. This architecture ensures that assets are versioned, processed, and published only when their owning page is built, maintaining a tight coupling between content and dependencies.

Accessing and Processing Bundle Resources in Templates

Page resources are accessible via the .Resources method in templates, returning a resource.Collection. This enables sophisticated asset pipelines directly within markup.

Basic Resource Retrieval

Access a specific image and resize it for responsive display:

{{ with .Resources.Get "hero.jpg" }}
  {{ with .Resize "800x" }}
    <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
  {{ end }}
{{ else }}
  {{ errorf "Missing hero image for %s" .File.Path }}
{{ end }}

Pattern Matching and Data Processing

Iterate over all JSON files in the bundle and unmarshal them for display:

{{ range .Resources.Match "**.json" }}
  {{ $data := . | transform.Unmarshal }}
  <pre>{{ $data | jsonify }}</pre>
{{ end }}

Configuring Resources via Front Matter

You can assign metadata to resources using the resources array in front matter, then reference them by name:


# content/posts/my-article/index.md

title: My Article
resources:
  - src: hero.jpg
    name: hero
    params:
      alt: "Cover illustration"
  - src: data.json
    title: "Article data"
{{ with .Resources.GetMatch "hero" }}
  <img src="{{ .RelPermalink }}" alt="{{ .Params.alt }}">
{{ end }}

Multilingual Sites and Headless Bundles

According to the Hugo source documentation, multilingual implementations handle page resources efficiently by not duplicating shared assets across language variants. Resources are attached once to the default language bundle and referenced from translations, except for non-Markdown formats that require language-specific processing.

Headless bundles provide a mechanism for organizing reusable assets without publishing a standalone page. By setting headless: true in a branch bundle's front matter, you create a resource repository accessible via site.GetPage but excluded from site navigation and output:


# content/partials/_index.md

headless: true
{{ $bundle := site.GetPage "partials" }}
{{ range $bundle.Resources.ByType "image" }}
  <img src="{{ .RelPermalink }}">
{{ end }}

Summary

  • Page bundles in gohugoio/hugo are directories containing index.md (leaf) or _index.md (branch) plus associated assets, processed via common/paths/pathparser.go.
  • Type classification occurs through ModifyPathBundleTypeResource, marking files as TypeContentResource and exposing types via BundleType() in resources/page/page.go.
  • Resource methods include Get, GetMatch, Match, and ByType, returning objects with RelPermalink, Resize, and custom parameters.
  • Multilingual efficiency prevents resource duplication across languages, while headless bundles enable unpublished asset collections for shared components.

Frequently Asked Questions

What is the difference between a leaf bundle and a branch bundle in Hugo?

A leaf bundle uses index.md and represents a single content page that cannot contain child pages, making it ideal for articles with dedicated assets. A branch bundle uses _index.md and represents a section list page that can contain nested pages and subsections. The distinction is enforced in common/paths/pathparser.go through the TypeLeaf and TypeBranch constants.

Can I use page bundles with multilingual Hugo sites?

Yes, Hugo's page bundle system supports multilingual sites efficiently. Page resources are attached to the default language bundle and referenced from other languages without duplication, except for non-Markdown formats that require language-specific processing. This architecture minimizes build overhead while maintaining asset localization capabilities.

How do I process images within a Hugo page bundle?

Images in page bundles are accessed via .Resources.Get or .Resources.GetMatch and implement the resource.Resource interface. You can chain image processing methods like .Resize, .Fit, or .Fill directly on the resource object before generating the RelPermalink, enabling on-the-fly image optimization without external build tools.

What is a headless bundle and when should I use it?

A headless bundle is a branch bundle with headless: true set in its front matter, which prevents Hugo from publishing a URL or rendering a page while keeping its resources accessible via site.GetPage. Use headless bundles to organize shared assets like logos or icon libraries that multiple templates reference but that should not exist as standalone pages on the site.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →