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 containsindex.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 pathGetMatch: Retrieves a resource by pattern or metadata nameMatch: Returns all resources matching a glob patternByType: 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/hugoare directories containingindex.md(leaf) or_index.md(branch) plus associated assets, processed viacommon/paths/pathparser.go. - Type classification occurs through
ModifyPathBundleTypeResource, marking files asTypeContentResourceand exposing types viaBundleType()inresources/page/page.go. - Resource methods include
Get,GetMatch,Match, andByType, returning objects withRelPermalink,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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →