Hello! Welcome back to our course on building complex websites with AI-assisted "vibe coding."
In our last lesson, we tackled a fundamental challenge for content-heavy sites: pagination. You learned how to use Hugo's .Paginate function and the Paginator object to split long lists of content into manageable pages. This is a crucial skill for ensuring good performance and user experience, something you'll appreciate from your extensive SEO background.
However, paginating content is only half the story. By default, Hugo sorts pages by weight, then date, then title. What if you need a different order? For instance, you might want to sort articles by a "last updated" date rather than their creation date, or sort a list of services by a custom "priority" field.
This lesson directly addresses that need. Our learning outcome is to modify an AI-generated template to add sorting capabilities based on front matter fields. We will explore how to take full control over the order in which your content is displayed.
We will cover:
- Two primary methods for sorting page collections in Hugo: the
sortfunction and the.ByParammethod. - How to sort by custom fields you define in your content's front matter.
- A common and important pitfall related to case sensitivity when sorting.
- How to combine sorting with the pagination logic we learned previously.
Sorting in Hugo Templates
In Hugo, the data for each page—like its title, date, and any custom data you add—is stored in its front matter. As you know from your experience with various CMS platforms, this metadata is key to organizing and presenting content dynamically. Hugo makes this data available in templates through variables.
To see how we access custom fields, let's look at a quick example from the article "From WordPress to Hugo, a mindset transition" by Regis Philibert.
From WordPress to Hugo, a mindset transition
This section of the article explains how Hugo uses front matter to handle custom fields, making them accessible in templates via the .Params object.
Read the section titled "Custom Fields". It draws a comparison to WordPress's Advanced Custom Fields (ACF) and shows how you access front matter data using .Params.
As the article shows, if you have this in your front matter:
---
title: "My Awesome Post"
priority: 1
---
You can access the priority value in your template with .Params.priority. Now, let's see how we use this to sort.
Method 1: The sort Function
The most direct way to sort a collection of pages is with Hugo's built-in sort function. Its basic structure looks like this:
sort COLLECTION "KEY" "DIRECTION"
COLLECTION: The group of pages you want to sort (e.g.,.Pages).KEY: The field to sort by. This can be a default field like"Title","Date", or"Weight", or a custom parameter from your front matter.DIRECTION(optional):"asc"for ascending (the default) or"desc"for descending.
So, to modify our list template to sort pages by title, we would change this:
{{ range .Pages }}
to this:
{{ range sort .Pages "Title" }}
To sort by a custom front matter field, like priority, you specify it using .Params:
{{ range sort .Pages ".Params.priority" }}
A Critical Pitfall: Case Sensitivity
This seems straightforward, but there is a crucial detail that trips up even experienced developers. Let's say your front matter uses camelCase for a custom date:
---
title: "Historical item 1"
historicalDate: 2019-07-01T00:00:00-04:00
---
You might intuitively try to sort using .Params.historicalDate. However, this will not work as expected. Let's see this issue play out in a real-world support thread from the Hugo community.
Sort by custom date parameter - support
This Hugo Discourse thread, titled "Sort by custom date parameter", shows a developer struggling with a sort order that isn't working. The solution reveals a key quirk of the sort function.
First, read the initial post to understand the problem: sorting by .Params.historicalDate is being ignored. Then, jump to the post that starts with "My goodness, this does the trick!" to see the simple but non-obvious solution. Finally, read the post from Hugo's maintainer (bep) where he confirms the behavior.
As you discovered, the solution is surprising: when using the sort function, the parameter name must be all lowercase, regardless of how it's capitalized in the front matter file.
- Front Matter:
historicalDate: ... - Template
sortfunction:{{ range sort . ".Params.historicaldate" }}
This is a specific requirement for the sort function (and where, which we'll see next lesson). When you access the parameter directly, like {{ .Params.historicalDate }}, you can use the original capitalization. It's a subtle distinction, but knowing it will save you a lot of debugging time.
Method 2: The .ByParam Method
Hugo often provides multiple ways to accomplish a task. An alternative, and often more readable, way to sort is by using the .ByParam method, which can be chained onto a page collection.
Let's return to the "From WordPress to Hugo" article to see this in action.
From WordPress to Hugo, a mindset transition
This section of the article demonstrates a more 'elegant Hugo variant' for querying and ordering pages. It introduces the .ByParam method.
Read the subsection titled "What about other pages?" Focus on the code block under "Let’s see its elegant Hugo variant". Notice how .ByParam "rating" is used.
The example shows this elegant line:{{ $recents := (where .Site.RegularPages "Type" "recipe").ByParam "rating" }}
Breaking this down, it gets a collection of pages and then sorts it using .ByParam "rating". Unlike the sort function, .ByParam is generally not case-sensitive, making it a bit more forgiving. To sort in reverse, you would use .Reverse.
Which method should you use?
- The
sortfunction is a general tool that works on different kinds of data, not just pages. .ByParamis a method specific to page collections and is often preferred for its readability when chaining commands.
For most use cases involving sorting pages by a front matter parameter, either will work. The key is to be aware of the sort function's case-sensitivity quirk.
Combining Sorting and Pagination
Now, let's integrate this with what we learned in the last lesson. The rule is simple: you must sort the pages before you paginate them.
If you paginate first, you are only sorting the small subset of pages on the current page, not the entire collection.
Here is the correct sequence in a list template:
{{/* layouts/_default/list.html */}}
{{/* 1. Define the pages to list, e.g., all pages in this section */}}
{{ $pages := .Pages }}
{{/* 2. Sort the full collection of pages. Here, we sort by 'lastUpdated' descending. */}}
{{ $sortedPages := sort $pages ".Params.lastupdated" "desc" }}
{{/* 3. Paginate the *sorted* collection. */}}
{{ $paginator := .Paginate $sortedPages }}
{{/* 4. Range over the paginator's pages for the current page. */}}
{{ range $paginator.Pages }}
<article>
<h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
<p>Last Updated: {{ .Params.lastUpdated.Format "January 2, 2006" }}</p>
</article>
{{ end }}
{{/* 5. Render the pagination controls. */}}
{{ partial "pagination.html" . }}
Vibe Coding: Prompting for Sorting
Let's translate this into our AI-assisted workflow. When you have a working list template and want to add sorting, a precise prompt is key.
Prompt:
I have a Hugo list template at
layouts/blog/list.htmlthat uses.Paginate .Pagesto create a paginated list of blog posts.Task: Modify this template to sort the posts before pagination.
Instructions:
- All my blog posts have a front matter field named
lastUpdatedinYYYY-MM-DDformat (e.g.,lastUpdated: 2024-05-10).- Sort the posts by the
lastUpdatedfield in descending order (newest first).- Make sure the sorting happens before the
.Paginatefunction is called.- Use the
sortfunction for this task.
This prompt works well because it specifies the file, the front matter field to use, the desired sort direction, and the correct logical order (sort then paginate).
Test your understanding!
You are building a "Products" section. Each product's Markdown file has a price field in its front matter, like price: 29.99.
You need to sort the products from the least expensive to the most expensive on the product listing page. Your template currently has this line:
{{ $paginator := .Paginate .Pages }}
How would you modify the template code to achieve this sorting before pagination? Write the two lines of code that should replace the single line above.
Show answer
The correct approach is to first sort .Pages by the price parameter, and then paginate the resulting sorted list.
{{ $sortedProducts := sort .Pages ".Params.price" "asc" }}
{{ $paginator := .Paginate $sortedProducts }}
Note:
- We use
.Params.price(all lowercase) to be safe with thesortfunction. - We explicitly use
"asc"for ascending order (cheapest first), though it is the default.
Conclusion
You have now gained a critical level of control over your content presentation. By mastering sorting based on front matter fields, you can create much more dynamic and user-centric list pages—ordering posts by popularity, products by price, or events by a custom date.
Key Takeaways:
- Hugo allows sorting page collections using either the
sortfunction or the.ByParammethod. - When using the
sortfunction with a custom parameter from front matter (e.g.,.Params.myField), the field name must be in all lowercase within the function call. - The logical order of operations is crucial: sort first, then paginate.
- You can specify sort direction with
"asc"(ascending) or"desc"(descending).
Preview of the next lesson:
Sorting gives us control over order, but what about control over inclusion? In our next lesson, we will explore the where function. This will allow us to filter our page collections, enabling us to create lists that only include pages that meet certain criteria—for example, showing only posts with a featured: true flag in their front matter.