Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 18

Adding Sorting to AI-Generated Templates

Recommended resources

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 sort function and the .ByParam method.
  • 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 sort function: {{ 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 sort function is a general tool that works on different kinds of data, not just pages.
  • .ByParam is 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.html that uses .Paginate .Pages to create a paginated list of blog posts.

Task: Modify this template to sort the posts before pagination.

Instructions:

  1. All my blog posts have a front matter field named lastUpdated in YYYY-MM-DD format (e.g., lastUpdated: 2024-05-10).
  2. Sort the posts by the lastUpdated field in descending order (newest first).
  3. Make sure the sorting happens before the .Paginate function is called.
  4. Use the sort function 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 the sort function.
  • 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 sort function or the .ByParam method.
  • When using the sort function 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.