How Hugo's Page Collection and Sorting System Works Internally: A Deep Dive into the Source Code
Hugo uses a global page map (tree-based storage) to index all content, queries it with predicates to build collections, caches results in a per-site pageCache, and sorts them using a stable closure-based sorter that respects ordinal, weight, date, and language-aware collation.
Hugo's renowned build speed relies on sophisticated content organization algorithms. The gohugoio/hugo repository implements a multi-layered page collection and sorting system that combines tree-based storage, predicate filtering, and aggressive memoization to handle sites with thousands of pages efficiently.
1. The Global Page Map Architecture
1.1 The pageMap Structure
All content in Hugo resides in a pageMap defined in [hugolib/content_map_page.go](https://github.com/gohugoio/hugo/blob/master/hugolib/content_map_page.go#L84-L102). This structure maintains a tree of contentNode objects (treePages and treeResources) indexed by content-path keys (e.g., "/blog/post1").
type pageMap struct {
// Main storage for all pages.
*pageTrees // treePages, treeResources, …
pageReverseIndex *contentTreeReverseIndex // simple look‑ups by filename
// … several dynacache partitions for memoised look‑ups
}
The tree structure enables efficient prefix searches, allowing Hugo to retrieve entire sections or subtrees without scanning the entire site.
1.2 Querying with Predicates
When templates request .RegularPages, Hugo invokes Site.RegularPages() in [hugolib/site.go](https://github.com/gohugoio/hugo/blob/master/hugolib/site.go#L48-L62). This method queries the page map using a predicate-based filter:
// hugolib/site.go – RegularPages()
func (s *Site) RegularPages() page.Pages {
s.CheckReady()
return s.pageMap.getPagesInSection(
pageMapQueryPagesInSection{
pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
Path: "",
KeyPart: "global",
Include: pagePredicates.ShouldListGlobal.
And(pagePredicates.KindPage).BoolFunc(),
},
Recursive: true,
},
)
}
The pagePredicates.KindPage ensures only regular content pages are returned, while ShouldListGlobal respects front-matter settings like draft and publishdate. The query constructs a cache key via pageMapQueryPagesInSection.Key() and checks the cachePages1/2 partitions before walking the tree.
2. The Sorting Engine
2.1 DefaultPageSort: The Canonical Ordering
Hugo's default sort order is defined by DefaultPageSort in [resources/page/pages_sort.go](https://github.com/gohugoio/hugo/blob/master/resources/page/pages_sort.go#L82-L87):
// DefaultPageSort is the default sort func for pages in Hugo:
// Order by Ordinal, Weight, Date, LinkTitle and then full file path.
DefaultPageSort = func(p1, p2 Page) bool {
// 1. Explicit ordinal set via front‑matter
o1, o2 := getOrdinals(p1, p2)
if o1 != o2 && o1 != -1 && o2 != -1 { return o1 < o2 }
// 2. Weight0 (taxonomy weight)
w01, w02 := getWeight0s(p1, p2)
if w01 != w02 && w01 != -1 && w02 != -1 { return w01 < w02 }
// 3. Date (newest first)
if p1.Weight() == p2.Weight() {
if p1.Date().Unix() == p2.Date().Unix() {
// 4. LinkTitle (language‑aware collator)
c := collatorStringCompare(func(p Page) string { return p.LinkTitle() }, p1, p2)
if c == 0 {
// 5. Full normalized file path as a final tiebreaker
return compare.LessStrings(p1.PathInfo().Path(), p2.PathInfo().Path())
}
return c < 0
}
return p1.Date().Unix() > p2.Date().Unix()
}
// … weight fallback omitted for brevity …
}
This stable sort uses sort.Stable and implements a five-tier hierarchy: ordinal (explicit front-matter), weight0 (taxonomy weight), date (descending), link title (language-aware collation), and file path (final tiebreaker).
2.2 Language-Aware Collation
When sorting by title or link title, Hugo uses langs.GetCollator1 from [langs/collator.go](https://github.com/gohugoio/hugo/blob/master/langs/collator.go) to ensure proper Unicode sorting according to the site's language settings. The collatorStringCompare function wraps this collator for use in the DefaultPageSort closure.
2.3 The pageCache (spc) for Memoization
To prevent redundant sorting operations, Hugo maintains a global pageCache instantiated as spc in [resources/page/pages_sort.go](https://github.com/gohugoio/hugo/blob/master/resources/page/pages_sort.go#L30):
var spc = newPageCache() // pages_sort.go:30
The newPageCache() function (see [pages_cache.go](https://github.com/gohugoio/hugo/blob/master/resources/page/pages_cache.go#L44-L52)) creates two dynacache partitions (cachePages1 and cachePages2). When a sort method like .ByTitle is invoked, it calls spc.get(key, sorter, pages), which either returns the cached slice or performs the sort, stores the result, and returns it. This makes repeated calls O(1) after the initial computation.
2.4 Shortcut Sort Methods
Convenience methods like ByTitle, ByWeight, and ByDate are thin wrappers around the same machinery. For example, ByTitle in [resources/page/pages_sort.go](https://github.com/gohugoio/hugo/blob/master/resources/page/pages_sort.go#L42-L53) builds a language-aware string sorter:
func (p Pages) ByTitle() Pages {
const key = "pageSort.ByTitle"
pages, _ := spc.get(key,
collatorStringSort(func(p Page) string { return p.Title() }), p)
return pages
}
All sort methods share:
- A
pageByclosure implementing comparison logic - A
pageSortersatisfyingsort.Interface - The
spccache for memoization
3. Practical Usage in Templates
3.1 Basic Page Collection
When you range over .Site.RegularPages in a template:
{{ range .Site.RegularPages }}
<a href="{{ .RelPermalink }}">{{ .Title }}</a><br>
{{ end }}
Behind the scenes, this triggers Site.RegularPages() → pageMap.getPagesInSection with the KindPage predicate. If no explicit sort is requested, Hugo applies DefaultPageSort to ensure consistent ordering.
3.2 Explicit Sorting by Title
To sort by title using language-aware collation:
{{ $pages := .Site.RegularPages.ByTitle }}
{{ range $pages }}
{{ .Title }} – {{ .RelPermalink }}
{{ end }}
This invokes Pages.ByTitle(), which uses collatorStringSort with langs.GetCollator1 for proper Unicode handling, then caches the result in spc.
3.3 Custom Sorting with Sort Function
For ad-hoc sorting in templates:
{{ $sorted := .Site.RegularPages.Sort "Date" "desc" }}
{{ range $sorted }}
{{ .Date.Format "2006-01-02" }} – {{ .Title }}
{{ end }}
The ns.Sort method (see [tpl/collections/sort.go](https://github.com/gohugoio/hugo/blob/master/tpl/collections/sort.go#L30-L56)) constructs a pageBy closure from the arguments and delegates to the pageSorter logic, maintaining consistency with the built-in sort methods.
4. Key Source Files Reference
Summary
- Tree-based storage: Hugo stores all content in a
pageMap(defined inhugolib/content_map_page.go) using path-indexed trees for efficient subtree queries. - Predicate filtering: Collections like
.RegularPagesare built by querying the map with boolean predicates (e.g.,pagePredicates.KindPage) that respect draft status and publication dates. - Hierarchical sorting: The
DefaultPageSortfunction implements a five-tier sort order: ordinal, weight0, date (descending), link title (language-aware), and file path. - Memoization: Both collection queries and sort operations are cached using the
pageCache(spc) to ensure O(1) retrieval on subsequent template calls during the same build. - Language awareness: Sorting uses
langs.GetCollator1to ensure proper Unicode collation according to site language settings.
Frequently Asked Questions
How does Hugo cache page collections to improve build performance?
Hugo implements a two-level caching strategy. First, the pageMap queries use cache partitions (cachePages1 and cachePages2) to store filtered collections like .RegularPages. Second, the pageCache (variable spc in resources/page/pages_sort.go) memoizes sorted slices. When a template calls .ByTitle or .ByWeight multiple times, Hugo returns the cached result after the first computation, making subsequent calls O(1).
What is the difference between .Pages and .RegularPages in Hugo's internal system?
Internally, .Pages typically returns all pages in a section including nested sections and taxonomy pages, while .RegularPages (implemented in hugolib/site.go) specifically filters for content pages of kind Page using the predicate pagePredicates.KindPage.And(pagePredicates.ShouldListGlobal). This ensures .RegularPages excludes section listings, taxonomy terms, and draft content unless explicitly configured otherwise.
How does Hugo handle sorting for multilingual sites with different character sets?
Hugo uses the langs.GetCollator1 function from langs/collator.go to create language-specific collators. When sorting by title or link title (as seen in DefaultPageSort and the ByTitle method), Hugo passes the strings through collatorStringCompare, which uses the appropriate Unicode collation rules for the site's language. This ensures that accented characters and non-Latin scripts sort correctly according to locale-specific rules rather than simple byte comparison.
Why does Hugo use a stable sort for page collections?
Hugo uses sort.Stable (via the pageSorter implementation) to preserve the relative order of pages that compare as equal. This is critical because DefaultPageSort uses a five-tier fallback system (ordinal → weight0 → date → link title → path). Without stable sorting, pages with identical weights and dates could shuffle randomly between builds, causing non-deterministic output. Stability ensures reproducible builds and consistent pagination across site regenerations.
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 →