Paginated Posts With Hugo
Setting It Up
The simplest solution is to create a custom layout to handle multipage
documents. For example, I created a multipage layout in my theme
as layouts/blog/multipage.html
. You can put it wherever
it makes sense for your structure.
First, though, you need a way to tell which page is which, because it won’t be obvious to Hugo.
I’m still chewing on the filename thing I mentioned earlier – I might actually try that later – but for now, we’re going to use the front matter in each page. The front matter for the page you’re reading right now looks like this:
---
page_number: 2
---
And that’s it. That’s all each page gets: its own page number. The only thing this is used for in my implementation is as a sort key to order the pages by. Simple enough; we can order them nicely that way.
Now we just need to make sure the front matter is set up properly in
_index.html
such that it activates our multipage layout:
---
date: 2023-03-09T00:00:00-06:00
slug: paginated-posts-with-hugo
title: "Paginated Posts With Hugo"
featured_image: library/documentation.png
url: /2023/03/paginated-posts-with-hugo
categories: ["Blogging"]
tags: ["Hugo", "HOWTO", "Tip"]
flags: ["featured"]
layout: multipage
_build:
render: always
cascade:
_build:
render: never
---
There are a few important things here, but for now the highlighted line is what we’re interested in: it selects the page template and ensures that the page is displayed just the way we want it. Unfortunately I don’t know of a way to do that automatically, so manual layout selection it is.
Laying It Out
Now we have the thing using our layout, but how does the layout work? How do we make it put all these pages in the right place? It’s actually surprisingy simple in the end.
List pages are intended to list things. That much is obvious. How you represent those things is entirely up to you. In the case of a multipage article, we’re just going to display each item, in full, one per page. We’ll use Hugo’s Paginator to do it.
For example, multipage.html
might look something like this:
{{- define "main" -}}
{{- $pages := .RegularPages.ByParam "page_number" -}}
{{- $paginator := .Paginate $pages 1 -}}
<div class="page-content">
{{- range $paginator.Pages -}}
{{- partial "page-content" . -}}
{{- end -}}
</div>
{{- if gt $paginator.TotalPages 1 -}}
<div class="paginator">
{{- template "_internal/pagination.html" . -}}
</div>
{{- end -}}
{{- end -}}
The first highlighted section here gathers up the pages, ordered by
the page_number
front matter field we talked about earlier, and then
begins the pagination. You can order your pages however you want
here; Hugo has simply handed you a slice of all the subpages in the
document. This is really a branch bundle, so that’s all handled
automatically.
The second highlighted section loops through the list of pages and provides the unique content associated with each one. The result will be however many copies of this template, each with the content from one page inserted where the loop is. How Hugo does that internally, I have no idea (and I’m rather curious). Note that a partial is used here; with good design, you can reuse that in your single template and only have to write it once.
Within that partial, your context is the target page, so you can
access .Content
and so forth as needed. Note, however, that
some properties will not be available (such as any permalink-related
properties; these pages don’t have proper permalinks!).
And finally, the last highlighted section adds the page controls if needed. The template used in this example (and on floating.io) is internal, provided by Hugo. You can certainly implement your own pager, but this is much easier if you’re using bootstrap or something with CSS that happens to be compatible with the bootstrap pager component.
And that’s it. Once you’ve got your template built, you can compile your site, navigate to your post, and see your nicely paged content!
That was too easy, right? So what’s the catch?