The Content Tree That Becomes a Site Graph

In Hugo, the most important question about a content file is not what its filename says, but what role its location and metadata give it in the site. This chapter explains how Hugo derives page kinds, URLs, ancestry, local resources, lifecycle rules, and taxonomy relationships from that structure. The practical payoff is predictability: once you can read the content tree as a model of the published site, you can usually anticipate what Hugo must assemble before it renders anything.

A simple file tree can therefore express more than storage. It can say that a document belongs to a section, that an image belongs to one page, that a page should appear in a term index, or that a branch is only a container for reusable material. Hugo's build system turns those signals into nodes and edges. The next step is to understand the signals one at a time, then see how they interact.

A path is a statement about page kind

Consider a small content tree:

content/
├── _index.md
├── about.md
└── posts/
    ├── _index.md
    ├── first-post.md
    └── launch/
        └── _index.md

The root _index.md represents the home page. The _index.md under posts represents a section page, and first-post.md is a regular page in that section. The launch directory becomes a nested section because it has its own _index.md. This is different from a directory used only to organize files: a section has logical ancestors and descendants and has a list page; an ordinary nested directory does not automatically acquire those behaviors.

That distinction gives Hugo several page kinds. A regular content page is a single page. A home page, section page, taxonomy page, or term page is a list-oriented page whose job often includes presenting other pages. The same template vocabulary can therefore operate at different levels: a single-page template renders one content item, while a list template renders a collection or index.

The special role of _index is easy to underestimate. It is not just a marker that tells Hugo a directory exists. It can carry title, description, menu information, and body content for the home, a section, a taxonomy, or a term. A section can consequently have an introduction of its own, not merely a list of descendants. Hugo's page lookup methods can retrieve that index page and its metadata as part of the page model.

A regular file also has several related path concepts. Suppose the file is content/posts/my-first-post.md. Its section is posts; its slug is normally my-first-post; its path describes where it sits under the content root; and its URL is the complete public path. With the default pretty-URL behavior, Hugo will ordinarily publish it at a path such as /posts/my-first-post/. A front matter slug can replace the final segment, while a front matter url can replace the entire URL path. Those are different controls: changing a slug changes the last component, while changing a URL changes the whole destination.

The path-to-URL rule is a default, not an absolute identity. Language prefixes, output formats, permalink configuration, and explicit front matter can all affect the final target. The useful principle is that Hugo starts with structure, then applies more specific declarations. This is why the source tree remains meaningful even in projects with customized URLs.

Front matter connects identity to behavior

The file path supplies a page's initial place in the graph; front matter supplies additional edges and decisions. Hugo documents front matter as metadata that describes content, augments it, establishes relationships, controls published structure, and influences template selection. It is better understood as a control plane for a page than as a collection of optional labels.

Some fields affect whether a node is eligible for publication. A page marked as a draft is not rendered by default. A future publication date delays rendering, and an expiry date can stop it after the specified time. These conditions are evaluated during the build, so publication status is not merely a filter applied after HTML has been generated.

Other fields affect identity and presentation. title and description provide content metadata; type can change the content type used for template selection; and layout can target a particular layout. A custom value such as an author name belongs under params, keeping user-defined data distinct from Hugo's reserved fields. Output requests and build options then add publication choices to the same page definition.

Inheritance is where front matter becomes visibly graph-shaped. A branch page can cascade parameters or build values to descendants. For example, a section can give every descendant a display parameter, or it can make a whole subtree headless by changing how descendants are listed and rendered. A cascade can target a path pattern instead of applying everywhere. The important consequence is that a change in an ancestor can alter the effective configuration of many pages, which is why a build must calculate inherited state rather than treating every file independently.

Lifecycle behavior has a similar edge case. A branch page that is a draft, future page, or expired page does not automatically prevent its descendants from being published. If the intended policy is to suppress the entire subtree, build options must be cascaded to the descendants. The distinction is practical: a branch page may be a navigational container whose own rendering status is separate from the pages below it.

Template selection follows the same principle of increasingly specific information. Hugo considers page kind, a user-selected layout, output format, language, type, and section when looking for a template. A page in the posts section may use a section-specific template; a page with an explicit layout can target a more specialized variant. The project and theme layout spaces participate in the lookup rather than forming two completely separate systems. The result is a page node carrying enough information for Hugo to choose a presentation without making the content file contain the presentation itself.

Bundles define resource ownership

Pages become more useful when their associated assets have a clear owner. A page bundle is a directory that encapsulates content and resources. A leaf bundle contains an index content file and has no descendant bundles. A branch bundle contains _index content and may contain descendants. The distinction is not decorative: it determines whether the directory is an individual page, a container, or both a container and a resource owner.

For example:

content/
└── reports/
    └── annual/
        ├── index.md
        ├── cover.jpg
        └── data.json

This is a leaf bundle. cover.jpg and data.json are page resources associated with the annual report. A template rendering that page can retrieve them through the page's resource collection, process the image, or unmarshal the data. The association is local and explicit: another page bundle cannot silently reach into this bundle and treat its files as its own resources.

A branch bundle works differently. A reports/_index.md file can describe the reports section while its descendants contain individual report pages. Resources adjacent to the branch index can belong to the branch page, but files inside descendant bundles belong to those descendants. Hugo's resource trees preserve that ownership boundary so that a resource can be resolved relative to the page that owns it.

Build options make ownership useful even when a page should not be published as a page. A headless leaf or branch bundle can be excluded from rendering and global lists while remaining accessible through a page lookup. A home-page template might retrieve a headless page, use its body or images, and explicitly publish the resources it needs. The output then contains the selected resources without an unwanted standalone HTML page.

This is a valuable separation between existence, listing, rendering, and resource publication. A node can exist in Hugo's page model, be omitted from normal page collections, avoid its own HTML file, and still supply content to another page. Treating all four outcomes as one boolean—published or unpublished—would lose an important part of Hugo's design.

The repository's internal page map reflects this distinction. It stores pages and bundled resources in path-oriented trees and provides separate lookup and cache structures for page collections and resources. That implementation does not change the public rule, but it explains why Hugo can answer both “which pages are in this section?” and “which files belong to this page?” without treating them as the same query.

Taxonomies add reverse edges

Sections describe containment: a page sits under a branch, and a branch sits under its ancestors. Taxonomies describe classification. A page can be assigned several terms, and each term can point back to many pages. If a movie page has the terms “science fiction” and “adaptation,” both terms can become ways to find that page without changing its place in the content tree.

Hugo calls the classification a taxonomy, the label within it a term, and the content assigned to a term a value. A project can declare taxonomies such as tags, categories, authors, or genres. When a taxonomy is used, Hugo can generate a page listing the terms and individual term pages listing the associated content. The result is a reverse relationship derived from front matter rather than a second copy of every content file.

That reverse relationship explains why taxonomy processing belongs in graph assembly. When Hugo reads a page's terms, it must connect the page to each term, account for the term's own page, and make the resulting collection available to templates. The internal assembler records taxonomy entries and their weights. A taxonomic weight can influence ordering, and the same content can therefore appear in different relative positions on a section page and a term page.

Terms can also be treated as content-rich objects. A corresponding term bundle can carry its own front matter and resources—for example, an author's affiliation and portrait. The taxonomy page can list those term pages, while the term page can present the author's metadata and associated content. The label is no longer just a string attached to a post; it has become another page in the model with its own presentation and resources.

There is a boundary here as well. A taxonomy is a flat set of terms by default, not an automatically inferred hierarchy of nested domain objects. If a project needs richer structure, it must express that structure through content, parameters, data, or templates. Hugo generates useful relationships from explicit terms; it does not infer every relationship a project might want.

Reading the graph before the build

The main relationships can now be made explicit:

erDiagram SECTION ||--o{ PAGE : contains PAGE ||--o{ RESOURCE : owns PAGE }o--o{ TAXONOMY_TERM : assigns PAGE }o--|| FRONT_MATTER : governed_by PAGE ||--o{ OUTPUT_PATH : produces FRONT_MATTER }o--o{ OUTPUT_PATH : shapes

A section contains pages and can have descendants. A page owns zero or more resources, and its front matter assigns taxonomy terms, modifies lifecycle or layout behavior, and influences the output path. The diagram is intentionally not a database schema: it is a compact way to show the edges Hugo must maintain while building the site.

Those edges also explain why a small source change can have a large effect. Renaming a page may change its slug and links. Changing a section index may alter a list page. Adding a taxonomy term can create or repopulate a term page. Changing a cascade can affect descendants. Adding an image to a bundle expands the resources available to one owner. The source change is local in the filesystem, but its consequences are determined by the relationships in the graph.

A useful design habit follows from this. Before adding a feature to a Hugo project, decide which relationship it needs. If the need is containment and navigation, use sections and index pages. If it is page-local asset ownership, use a bundle. If it is reverse discovery across pages, use a taxonomy. If it is inherited policy, use a cascade. If it is reusable but non-standalone material, consider a headless bundle. These are not interchangeable ways to store files; each creates a different kind of graph edge.

That does not eliminate configuration work. URLs can be overridden, page kinds can be disabled, templates can specialize, and build options can separate listing from rendering. But it gives the configuration a place in the model. Hugo is not guessing what a directory “means” from a visual convention alone; it is applying documented rules to construct page state.

Once the page and resource graph exists, Hugo still has to decide what has changed, which relationships must be assembled, which pages are eligible, and which templates and output formats should run. The next question is therefore temporal rather than structural: what does Hugo actually do between discovering these source nodes and writing a correct page to the publication directory?

References