Paginated Posts With Hugo

SHARE
March 9, 2023

Caveat Emptor

So far so good, right? We have paged documents. We can access our attached media with little issue. This is everything we’ve been waiting for!

Isn’t it?

Oh, wait…

Yeah, there’s a couple of other issues…

Hugo has some fairly reasonable Permalink format support. For example, you can specify that all your blog posts should have the month and year as elements of the path. It’s a very handy thing, and with it you no longer have to manually manage your URLs.

Until you add our multipage hack.

There is, unfortunately, a deficiency in the permalink handling: it only applies to documents. And remember, our multipage post isn’t actually a document; it’s a list. And in Hugo land, lists don’t use the pattern-based Permalink generator.

Unfortunately, there is very little to be done. The Hugo folks are aware of the problem, and there have been murmurs of possible fixes (which I anxiously await!). In the mean time, we have to work around it.

And now you know why the url is explicitly specified in the front matter for this post, as shown earlier. It’s necessary to keep the path consistent with all the non-paged blog posts on the site.

Extra Pages? What?

Of course, if you’ve generated your site without configuring your bundle’s front matter similar to what I showed earlier, you might notice a few extra pages, possibly in the /page directory. If you look in them, you’ll find them to be a random collection of various pages from your multipage documents.

Yeah, Hugo is trying to generate those as actual pages, and it’s not doing it very well. Hence this part of the bundle’s front matter:

_build:
  render: always
cascade:
  _build:
    render: never

This little snippet tells Hugo to always generate the list, but to never generate the pages it’s referencing. It’s a little counter-intuitive, but it works. Just make sure to include this in your archetype for multipage posts so you don’t have to remember it all the time…

Strangely, I can’t reproduce the issue this corrects on a minimal setup. I’m not sure where it’s coming from, but rest assured, it will probably bite you eventually if you don’t do this!

What About These Pages is Regular?!

Another thing you’ll have to watch out for is that every single page of your nifty multipage document is going to show up in .Site.RegularPages and the like – and the main “Post Page” – the page bundle itself – will not. That requires a bit of gymnastics in places.

You’re going to have to do your own RSS template, for example. The top of mine (which I modified from the default) looks like this:

{{- $pages := where (where site.RegularPages "Type" "blog") "Params.page_number" nil -}}
{{- $pages  = union $pages (where site.Pages "Layout" "multipage") -}}
{{- $pages = $pages.ByDate.Reverse -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{-   $pages = $pages | first $limit -}}
{{- end -}}

It’s a bit of a mouthful, yes? Basically, grab the blog pages, filter out anything that has a page number assigned since it shouldn’t be there, then add in any multipage documents, and sort the result. The rest is the limit feature from the standard RSS feed template.

This is not the only place you’ll have to do this. Any list page that grabs your list of blog posts (such as your home page!) will need this. Me? I’m going to refactor it out into a partial since it’s used in so many places…

Fun times.

Conclusion

In the end, in spite of the gotchas, this mechanism works fairly well – and for me, the ability to have multi-paged posts is important, so it’s also totally worth it; I don’t need them all that often, but sometimes it’s necessary. This was the main negative for choosing Hugo, and this technique works around it nicely.

Your own mileage will, of course, vary. Hopefully this post will help you avoid the fun that I had to go through to make it all work reliably.

Good luck!