Pagination: using pager values within the head element

Objective

Render something like this, from a base template, for each pager of a paginated collection:

<head>
  ...
  <link rel="prev" href="http://localhost:1313/books/page/2/">
  <link rel="next" href="http://localhost:1313/books/page/4/">
  <link rel="canonical" href="http://localhost:1313/books/page/3/">
  <title>Books - Page 3 | My Site</title>
  ...
</head>

Challenge

To get the URL of the next, previous, and current pager you must call the Paginator method on a Page object, but that also invokes pagination. The result of pagination is cached, so you can’t subsequently change (filter, sort, or group) the page collection in a list template (home, section, taxonomy, or term).

Our goal is to define the page collection and pager size (number of pages per pager) within a list template, but access the paginator from a base template before rendering the list. This sounds paradoxical (chicken/egg), but is easy to implement using Hugo’s base/block construct.

Approach

There are three pieces to this approach:

  1. The base template
  2. The list template
  3. The partial that renders the link elements within the head element
layouts/_default/baseof.html
<head>
  ...
  {{ block "pagination" . }}{{ end }}
  {{ partial "head/link-and-title-elements.html" . }}
  ...
</head>

layouts/posts/list.html
{{ define "pagination" }}
  {{ $pages := .Pages.ByTitle }}
  {{ .Store.Set "paginator" (.Paginate $pages 5) }}
{{ end }}

{{ define "main" }}
  <h1>{{ .Title }}</h1>
  {{ .Content }}

  {{ with .Store.Get "paginator" }}
    {{ range .Pages }}
      <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
    {{ end }}
    {{ template "_internal/pagination.html" $ }}
  {{ else }}
    {{ range .Pages }}
      <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
    {{ end }}
  {{ end }}

{{ end }}

If you’re not going to paginate a given list template, do not define a “pagination” block in the above, or leave its contents empty.

layouts/partials/head/link-and-title-elements.html
{{- $canonicalURL := .Permalink }}
{{- $title := printf "%s | %s" .Title site.Title }}
{{- if .IsHome }}
  {{- $title = site.Title }}
{{- end }}

{{- with .Store.Get "paginator" }}
  {{- $canonicalURL = .URL | absLangURL }}
  {{- if gt .TotalPages 1 }}
    {{- $title = printf "%s - Page %d | %s" $.Title .PageNumber site.Title }}
    {{- if $.IsHome }}
      {{- $title = printf "%s - Page %d" site.Title .PageNumber }}
    {{- end }}
  {{- end }}
  {{- with .Prev }}
    <link rel="prev" href="{{ .URL | absLangURL }}">
  {{- end }}
  {{- with .Next }}
    <link rel="next" href="{{ .URL | absLangURL }}">
  {{- end }}
{{- end }}

<link rel="canonical" href="{{ $canonicalURL }}">
<title>{{- $title }}</title>

Example

This example contains three list templates: one for a “books” section, one for a “films” section, and one for a “sculptures” section. Two of the list pages are paginated, but each with different settings. The other list page is not paginated.

git clone --single-branch -b hugo-forum-topic-50340 https://github.com/jmooring/hugo-testing hugo-forum-topic-50340
cd hugo-forum-topic-50340
hugo server

Related

This approach addresses this (somewhat) related issue:
https://github.com/gohugoio/hugo/issues/4507

3 Likes