Hello! Welcome back.
In the previous lesson, we built our first dynamic list page, using range to display all the content from a specific section. This is a great start, but it has a practical limit. Imagine a blog with hundreds of posts; a single page listing them all would be slow to load and difficult for a user to navigate. From your SEO perspective, you'll immediately recognize the negative impact this would have on Core Web Vitals and overall user experience.
Today, we'll solve that problem. This lesson focuses on a crucial feature for any content-heavy site: pagination. Our learning outcome is to implement pagination on a list page using Hugo's built-in Paginator object. We'll see how to break up long lists into manageable, user-friendly pages.
We will cover:
- How to configure pagination for your Hugo site.
- The role of Hugo's Paginator object and how to create it.
- How to add pagination controls using both a built-in partial and a custom-built template.
- How to prompt an AI to handle these modifications for you.
What is Pagination?
Pagination is the process of dividing a document into discrete pages. On a website, it's the familiar "1, 2, 3, Next" navigation you see at the bottom of blog indexes, product catalogs, or search results.

Proper pagination improves site usability, reduces page load times, and helps search engines crawl your content more efficiently.
To get familiar with Hugo's terminology and a few key configuration settings, let's start with the official documentation.
The Hugo documentation on "Pagination" provides the essential concepts and configuration options. It defines the key terms Hugo uses and shows you where to set site-wide pagination rules.
First, read the introduction and the "Terminology" section to understand the concepts. Then, review the "Structure" section, paying close attention to the hugo.toml configuration block. This shows how you can set the default number of items per page (pagerSize) site-wide.
As you saw in the reading, you can set the number of items per page globally in your hugo.toml file:
// hugo.toml
[pagination]
pagerSize = 5
With this setting, any paginated list will default to showing 5 items per page.
Implementing Pagination in Your Template
Now for the implementation. Hugo makes pagination straightforward by providing a powerful Paginator object. You create this object in your template, and it gives you everything you need: the list of items for the current page, and all the information required to build the navigation links.
There are two slightly different methods to create the paginator, .Paginator and .Paginate. We will focus on .Paginate because it's more flexible and allows you to modify or filter the content before paginating it, which will be useful later.
Let's modify the list template we conceptualized in the last lesson.
Before (listing all pages):
{{/* layouts/_default/list.html */}}
{{ range .Pages }}
<article>
<h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
</article>
{{ end }}
After (implementing pagination):
{{/* layouts/_default/list.html */}}
{{ $paginator := .Paginate .Pages }}
{{ range $paginator.Pages }}
<article>
<h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
</article>
{{ end }}
{{/* We'll add navigation here */}}
Notice the two key changes:
- We call
.Paginate .Pagesand store the result in a variable called$paginator. This creates the Paginator object. You can also override the default page size here, for example:.Paginate .Pages 10. - Our
rangeloop now iterates over$paginator.Pages, not.Pages. This ensures it only loops through the items for the current page.
Adding Pagination Controls: The Easy Way
A paginated list is useless without navigation. Hugo provides an embedded partial that makes adding navigation incredibly simple.
The official Hugo documentation explains how to use the convenient, built-in partial for pagination.
Read the "Navigation" section. Focus on the first part that introduces {{ partial "pagination.html" . }}. This is the simplest method for adding pagination controls.
By adding just one line to our template, we get a fully functional navigation element.
Complete list template with pagination:
{{/* layouts/_default/list.html */}}
<main>
<h1>{{ .Title }}</h1>
{{ $paginator := .Paginate .Pages }}
{{ range $paginator.Pages }}
<article class="post-preview">
<h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
<p>{{ .Summary }}</p>
</article>
{{ end }}
{{ partial "pagination.html" . }}
</main>
With this template, Hugo will now render your list page with the correct subset of content and a "Next," "Previous," and page number navigation at the bottom.
Vibe Coding: Prompting for Pagination
Let's apply our "vibe coding" workflow. Instead of writing this code manually, you can ask your AI assistant to modify the existing template.
Prompt:
I have a Hugo list template at
layouts/_default/list.html. It currently usesrange .Pagesto show all posts from a section.Task: Modify this template to add pagination.
Instructions:
- Use the
.Paginatefunction on.Pagesto create a paginator object. Let's show 10 items per page.- Update the
rangeloop to use the paginator's pages.- After the loop, include Hugo's built-in
pagination.htmlpartial to render the navigation controls.
This prompt is effective because it clearly states the file, the current state, and the specific Hugo functions and partials to use.
Building Custom Pagination Controls
The built-in partial is fast, but it may not fit your site's design. For a "complex website," you'll likely want full control over the HTML and CSS of your pagination component.

To build a custom component, you'll create your own layouts/partials/pagination.html file. Inside this file, you can use various properties of the Paginator object to build your navigation.
Some of the most useful properties are:
.HasPrev:trueif there is a previous page..Prev.URL: The URL of the previous page..HasNext:trueif there is a next page..Next.URL: The URL of the next page..PageNumber: The number of the current page..TotalPages: The total number of pages..First.URLand.Last.URL: URLs for the first and last pages.
The following resource provides a fantastic step-by-step guide to building a custom pagination partial from scratch.
The tutorial "Hugo - Pagination - Simple" provides a clear, practical walkthrough of creating a custom pagination partial. It builds the component piece by piece, explaining the logic at each step.
Follow this tutorial through to see how a custom pagination partial is built. Start with section 3, 'Layout', to see how the list template is modified. Then, read sections 4, 5, and 6 to see how 'Previous/Next', 'First/Last', and a page number indicator are added using the Paginator object's properties. Finally, review the complete code in section 7, 'Summary'.
By following that guide, you can create a fully custom and stylable pagination.html partial that looks something like this:
{{ $p := . }}
{{ if gt $p.TotalPages 1 }}
<nav aria-label="Page navigation">
<ul class="pagination">
<!-- First Page -->
{{ if ne $p.PageNumber 1 }}
<li class="page-item"><a class="page-link" href="{{ $p.First.URL }}">First</a></li>
{{ end }}
<!-- Prev Page -->
{{ if $p.HasPrev }}
<li class="page-item"><a class="page-link" href="{{ $p.Prev.URL }}">Previous</a></li>
{{ end }}
<!-- Page numbers would go here -->
<!-- Next Page -->
{{ if $p.HasNext }}
<li class="page-item"><a class="page-link" href="{{ $p.Next.URL }}">Next</a></li>
{{ end }}
<!-- Last Page -->
{{ if ne $p.PageNumber $p.TotalPages }}
<li class="page-item"><a class="page-link" href="{{ $p.Last.URL }}">Last</a></li>
{{ end }}
</ul>
</nav>
{{ end }}
This gives you complete control over the structure and classes, allowing you to apply any CSS you wish.
Test your understanding!
Imagine you're creating a very simple blog design. You don't want page numbers, just "Older posts" and "Newer posts" links.
"Older posts" should link to the next page (e.g., from page 1 to 2).
"Newer posts" should link to the previous page (e.g., from page 2 to 1).
Write an AI prompt to generate a custom Hugo pagination partial named layouts/partials/simple-nav.html that does this.
Show answer
Here's a strong prompt for that task:
Prompt:
I need a custom Hugo pagination partial. Please create the code for a file to be saved at
layouts/partials/simple-nav.html.Task: Generate a navigation component using Hugo's Paginator object.
Instructions:
- The navigation should be wrapped in a
<nav class="post-nav">tag.- If a previous page exists, show a link with the text "Newer posts" that points to
.Prev.URL. The link should have a class ofprev-link.- If a next page exists, show a link with the text "Older posts" that points to
.Next.URL. The link should have a class ofnext-link.- Do not include page numbers or first/last links.
This prompt is effective because it specifies the file path, the exact link text, the Hugo variables to use for the URLs (.Prev.URL, .Next.URL), and the conditional logic (if a page exists).
Conclusion
You've now added a fundamental feature to your web development toolkit. By implementing pagination, you've made your list pages more scalable, user-friendly, and performant—all key considerations for building the complex, content-rich sites you're aiming for.
Key Takeaways:
- Pagination is enabled in
hugo.tomlusingpagerSizeand implemented in list templates. - The
.Paginatefunction creates aPaginatorobject that splits your content across multiple pages. - You must update your
rangeloop to iterate over$paginator.Pages. - For quick results,
{{ partial "pagination.html" . }}uses Hugo's built-in navigation. - For full design control, you can build a custom partial using Paginator properties like
.HasNext,.Next.URL,.PageNumber, and.TotalPages.
Preview of the next lesson:
Our list pages now handle large amounts of content gracefully. But what controls the order of that content? By default, Hugo sorts by weight, then date, then title. In the next lesson, we will learn how to take control of this and modify an AI-generated template to add sorting capabilities based on front matter fields.