HUGO
News Docs Themes Community GitHub

Pagination

Split a list page into two or more subsets.

Displaying a large page collection on a list page is not user-friendly:

  • A massive list can be intimidating and difficult to navigate. Users may get lost in the sheer volume of information.
  • Large pages take longer to load, which can frustrate users and lead to them abandoning the site.
  • Without any filtering or organization, finding a specific item becomes a tedious scrolling exercise.

Improve usability by paginating home, section, taxonomy, and term pages.

The most common templating mistake related to pagination is invoking pagination more than once for a given list page. See the caching section below.

Terminology

paginate
To split a list page into two or more subsets.
pagination
The process of paginating a list page.
pager
Created during pagination, a pager contains a subset of a list page and navigation links to other pagers.
paginator
A collection of pagers.

Configuration

Control pagination behavior in your site configuration. These are the default settings:

pagination:
  disableAliases: false
  pagerSize: 10
  path: page
[pagination]
  disableAliases = false
  pagerSize = 10
  path = 'page'
{
   "pagination": {
      "disableAliases": false,
      "pagerSize": 10,
      "path": "page"
   }
}
disableAliases
(bool) Whether to disable alias generation for the first pager. Default is false.
pagerSize
(int) The number of pages per pager. Default is 10.
path
(string) The segment of each pager URL indicating that the target page is a pager. Default is page.

With multilingual sites you can define the pagination behavior for each language:

languages:
  de:
    contentDir: content/de
    languageCode: de-DE
    languageDirection: ltr
    languageName: Deutsch
    pagination:
      disableAliases: true
      pagerSize: 20
      path: blatt
    weight: 2
  en:
    contentDir: content/en
    languageCode: en-US
    languageDirection: ltr
    languageName: English
    pagination:
      disableAliases: true
      pagerSize: 10
      path: page
    weight: 1
[languages]
  [languages.de]
    contentDir = 'content/de'
    languageCode = 'de-DE'
    languageDirection = 'ltr'
    languageName = 'Deutsch'
    weight = 2
    [languages.de.pagination]
      disableAliases = true
      pagerSize = 20
      path = 'blatt'
  [languages.en]
    contentDir = 'content/en'
    languageCode = 'en-US'
    languageDirection = 'ltr'
    languageName = 'English'
    weight = 1
    [languages.en.pagination]
      disableAliases = true
      pagerSize = 10
      path = 'page'
{
   "languages": {
      "de": {
         "contentDir": "content/de",
         "languageCode": "de-DE",
         "languageDirection": "ltr",
         "languageName": "Deutsch",
         "pagination": {
            "disableAliases": true,
            "pagerSize": 20,
            "path": "blatt"
         },
         "weight": 2
      },
      "en": {
         "contentDir": "content/en",
         "languageCode": "en-US",
         "languageDirection": "ltr",
         "languageName": "English",
         "pagination": {
            "disableAliases": true,
            "pagerSize": 10,
            "path": "page"
         },
         "weight": 1
      }
   }
}

Methods

To paginate a home, section, taxonomy, or term page, invoke either of these methods on the Page object in the corresponding template:

The Paginate method is more flexible, allowing you to:

  • Paginate any page collection
  • Filter, sort, and group the page collection
  • Override the number of pages per pager as defined in your site configuration

By comparison, the Paginator method paginates the page collection passed into the template, and you cannot override the number of pages per pager.

Examples

To paginate a list page using the Paginate method:

{{ $pages := where site.RegularPages "Type" "posts" }}
{{ $paginator := .Paginate $pages.ByTitle 7 }}

{{ range $paginator.Pages }}
  <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ end }}

{{ template "_internal/pagination.html" . }}

In the example above, we:

  1. Build a page collection
  2. Sort the page collection by title
  3. Paginate the page collection, with 7 pages per pager
  4. Range over the paginated page collection, rendering a link to each page
  5. Call the embedded pagination template to create navigation links between pagers

To paginate a list page using the Paginator method:

{{ range .Paginator.Pages }}
  <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ end }}

{{ template "_internal/pagination.html" . }}

In the example above, we:

  1. Paginate the page collection passed into the template, with the default number of pages per pager
  2. Range over the paginated page collection, rendering a link to each page
  3. Call the embedded pagination template to create navigation links between pagers

Caching

The most common templating mistake related to pagination is invoking pagination more than once for a given list page.

Regardless of pagination method, the initial invocation is cached and cannot be changed. If you invoke pagination more than once for a given list page, subsequent invocations use the cached result. This means that subsequent invocations will not behave as written.

When paginating conditionally, do not use the compare.Conditional function due to its eager evaluation of arguments. Use an if-else construct instead.

Grouping

Use pagination with any of the grouping methods. For example:

{{ $pages := where site.RegularPages "Type" "posts" }}
{{ $paginator := .Paginate ($pages.GroupByDate "Jan 2006") }}

{{ range $paginator.PageGroups }}
  <h2>{{ .Key }}</h2>
  {{ range .Pages }}
    <h3><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h3>
  {{ end }}
{{ end }}

{{ template "_internal/pagination.html" . }}

As shown in the examples above, the easiest way to add navigation between pagers is with Hugo’s embedded pagination template:

{{ template "_internal/pagination.html" . }}

The embedded pagination template has two formats: default and terse. The above is equivalent to:

{{ template "_internal/pagination.html" (dict "page" . "format" "default") }}

The terse format has fewer controls and page slots, consuming less space when styled as a horizontal list. To use the terse format:

{{ template "_internal/pagination.html" (dict "page" . "format" "terse") }}

To override Hugo’s embedded pagination template, copy the source code to a file with the same name in the layouts/partials directory, then call it from your templates using the partial function:

{{ partial "pagination.html" . }}

Create custom navigation components using any of the Pager methods:

First
Returns the first pager in the pager collection.
HasNext
Reports whether there is a pager after the current pager.
HasPrev
Reports whether there is a pager before the current pager.
Last
Returns the last pager in the pager collection.
Next
Returns the next pager in the pager collection.
NumberOfElements
Returns the number of pages in the current pager.
PageGroups
Returns the page groups in the current pager.
PageNumber
Returns the current pager’s number within the pager collection.
Pagers
Returns the pagers collection.
PagerSize
Returns the number of pages per pager.
Pages
Returns the pages in the current pager.
PageSize
Returns the number of pages per pager.
Prev
Returns the previous pager in the pager collection.
TotalNumberOfElements
Returns the number of pages in the pager collection.
TotalPages
Returns the number of pagers in the pager collection.
URL
Returns the URL of the current pager relative to the site root.

Structure

The example below depicts the published site structure when paginating a list page.

With this content:

content/
├── posts/
│   ├── _index.md
│   ├── post-1.md
│   ├── post-2.md
│   ├── post-3.md
│   └── post-4.md
└── _index.md

And this site configuration:

pagination:
  disableAliases: false
  pagerSize: 2
  path: page
[pagination]
  disableAliases = false
  pagerSize = 2
  path = 'page'
{
   "pagination": {
      "disableAliases": false,
      "pagerSize": 2,
      "path": "page"
   }
}

And this section template:

{{ range (.Paginate .Pages).Pages }}
  <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ end }}

{{ template "_internal/pagination.html" . }}

The published site has this structure:

public/
├── posts/
│   ├── page/
│   │   ├── 1/
│   │   │   └── index.html  <-- alias to public/posts/index.html
│   │   └── 2/
│   │       └── index.html
│   ├── post-1/
│   │   └── index.html
│   ├── post-2/
│   │   └── index.html
│   ├── post-3/
│   │   └── index.html
│   ├── post-4/
│   │   └── index.html
│   └── index.html
└── index.html

To disable alias generation for the first pager, change your site configuration:

pagination:
  disableAliases: true
  pagerSize: 2
  path: page
[pagination]
  disableAliases = true
  pagerSize = 2
  path = 'page'
{
   "pagination": {
      "disableAliases": true,
      "pagerSize": 2,
      "path": "page"
   }
}

Now the published site will have this structure:

public/
├── posts/
│   ├── page/
│   │   └── 2/
│   │       └── index.html
│   ├── post-1/
│   │   └── index.html
│   ├── post-2/
│   │   └── index.html
│   ├── post-3/
│   │   └── index.html
│   ├── post-4/
│   │   └── index.html
│   └── index.html
└── index.html