Colocate content and assets with Hugo leaf page bundles
Keep images and Markdown together in a Hugo leaf bundle so pages stay portable and assets publish reliably.
30 Mar 2026, 17:44 UTC

Problem: assets drift away from their pages
Storing an image in static/ and linking it from a Markdown page separates the asset from the content. If you move the page to another site or duplicate the section, the image stays behind or gets copied by hand. Broken links appear when the copy is incomplete.
Takeaway: use a Hugo leaf page bundle. Place index.md and its images in the same folder under content/. Hugo renders index.md as the page and exposes sibling files as page resources, so the page and its assets travel together.
How a leaf bundle works
A leaf page bundle is a directory containing exactly one index.md plus optional assets such as images or CSS. Hugo treats the directory as one page. Files beside index.md become page resources available through .Resources in templates. You can link a resource directly with .RelPermalink or process it with an image filter such as Fill.
This differs from a branch bundle, which uses _index.md and represents a section rather than a single page. Confusing the two changes which resources a template can access.
Worked example: one post, one photo
Create this tree in your project:
content/trip/
├── index.md
└── photo.jpg
Add front matter and body to index.md:
---
title: "Trip"
date: 2026-03-01
---
# Trip
Here is a photo from the trip.
In a single-page template such as layouts/_default/single.html, reference the photo:
{{ $img := .Resources.GetMatch "photo.jpg" }}
{{ with $img.Fill "800x600" }}
{{ end }}
GetMatch locates photo.jpg inside the current bundle. Fill builds an 800 by 600 variant at build time. .RelPermalink outputs the relative URL for the processed image. If photo.jpg is missing, $img is nil and the with block renders nothing.
Build and verify
Run these commands from the project root in a terminal. You need normal read and write access to the project folder; no elevated permissions are required.
hugo version
hugo --minify
Expected result: Hugo writes public/trip/index.html and the processed image appears somewhere under public/trip/. Open the HTML and confirm an img tag points to a file in that directory.
Risk: if the template references a resource that does not exist, the build does not fail; the image tag is simply absent. Check the rendered HTML rather than assuming the asset published.
Limits and common mistakes
- A leaf bundle allows only one
index.md. Other Markdown files in the same folder become separate pages. - Assets are not published unless a template references them or an image filter processes them. An unused file in the bundle stays out of
public/. - Calling
.Resourceson a page outside a bundle returns nil. - Storing assets in
static/instead of the bundle reduces portability and duplicates path management.
Practical checks
- Create
content/trip/index.mdandcontent/trip/photo.jpg. - Add the template snippet above.
- Run
hugoand inspectpublic/trip/index.html. - Confirm the image markup references a file under
public/trip/. - Add
content/trip/unused.jpgwithout referencing it, rebuild, and confirm it does not appear underpublic/trip/.
Version note
Resource handling and image filters can differ between Hugo releases. Run hugo version and review the release notes for your installed version before depending on image transforms in production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.