Lessons

Lesson 7 - Pagination

a succulent plant with pale green and red dappling

There appears to be a bug where the Nunjucks templating language can't be rendered properly in <pre> blocks. Because of this, I will be excerpting the curly bracket-percent sign combos and replacing them with only percent signs. Wherever you see a percent sign in .njk code, add the relevant opening or closing curly bracket. I will also be replacing the double curly brackets with single curly brackets.

Simple pagination

Post pagination in Eleventy is pretty straightforward, mostly requiring some specific front matter.

The home page pagination I have set up here looks like the following (in index.njk):

---
pagination:
  data: collections.posts
  size: 6
  alias: posts
  reverse: true
---

6 posts per page, paginate data from collections.posts which we'll call just posts for short, and do it in reverse (aka, most recent posts show first).

You'll also likely want previous and next buttons. I did. Here's what I have:

% if pagination.href.previous or pagination.href.next %
<nav aria-label="pagination">
  <ol class="pagination">
    <li>
      % if pagination.href.previous %
      <a href="{ pagination.href.previous }"><< Previous</a>
      % else %
      << Previous
      % endif %
    </li>
    <li>
      % if pagination.href.next %
      <a href="{ pagination.href.next }">Next >></a>
      % else %
      Next >>
      % endif %
    </li>
  </ol>
</nav>
% endif %

Complex pagination

However, there's a catch. Tag pages are created via pagination! It's a lot harder to paginate those - you can't just use the front matter to set it up.

I untangled this GitHub issue about double-layered pagination and came to the following solution...

eleventy.config.js

In eleventy.config.js:

// note that this uses the lodash.chunk method, so you’ll have to require that
eleventyConfig.addCollection("doublePagination", function(collection) {
  // Get unique list of tags
  let tagSet = new Set(collection.getAllSorted().flatMap((post) => post.data.tags || []));

  // Get each item that matches the tag
  let paginationSize = 6;
  let tagMap = [];
  let tagArray = [...tagSet];
  for( let tagName of tagArray) {
    let tagItems = collection.getFilteredByTag(tagName);
    let pagedItems = lodashChunk(tagItems.reverse(), paginationSize);			// console.log( tagName, tagItems.length, pagedItems.length );
    for( let pageNumber = 0, max = pagedItems.length; pageNumber < max; pageNumber++) {
      tagMap.push({
        tagName: tagName,
        pageNumber: pageNumber,
        pageSize: pagedItems.length,
        pageData: pagedItems[pageNumber]
      });
    }
  }

  //console.log( tagMap );
  return tagMap;
});

tag-pages.njk

In my tag-pages.njk file (or whatever you use to template out your tag pages):

---
pagination:
  data: collections.doublePagination
  size: 1
  alias: tag
eleventyComputed:
  permalink: /tags/{ tag.tagName | slugify }/% if tag.pageNumber %{ tag.pageNumber + 1 }/% endif %
  title: "Posts tagged { tag.tagName }"
eleventyExcludeFromCollections: true
---
<h1>Posts tagged “{ tag.tagName }”</h1>

% set postlist = tag.pageData %
% include "postlist.njk" %

<nav aria-label="pagination">
  <ol class="pagination">
    <li>
      % if tag.pageNumber > 0 %
      <a href="{ pagination.href.previous }">Previous</a>
      % else %
      Previous
      % endif %
    </li>
    <li>
      % if tag.pageNumber < tag.pageSize - 1 %
      <a href="{ pagination.href.next }">Next</a>
      % else %
      Next
      % endif %
    </li>
  </ol>
</nav>

Note the pagination checking tag.pageNumber against tag.PageSize - the original suggested solution in the GitHub post creates an issue where the pagination loops through all of the tag pages bit-by-bit. This sorts that - hat tip to TheReyzar who mentioned the issue and showed part of their solution.

filters.js

Finally, in my filters.js file, I add the doublePagination tag to the tags that get filtered using filterTagList:

eleventyConfig.addFilter("filterTagList", function filterTagList(tags) {
  return (tags || []).filter(tag => ["all", "posts", "doublePagination"].indexOf(tag) === -1);
});