In the last Devlog, I talked about Jekyll collections and how I’m using them. As your site grows older, and you write more and more things, across a variety of collections, your pages which list-out content become more and more unwieldy, thus necessitating things like pagination. I encountered this issue some time ago - notably, with my Notebook, Changelog & Activity pages, each having 100’s of entries respectively. When not paginated, this results in not only very long pages that require quite a bit of scrolling to navigate, but also yielded pages that were very ill-performant, especially on mobile. My iPhone would nearly freeze trying to load, render or navigate away from these huge pages. So how can I mitigate this? Pagination of course!

Pagination is great, but unfortunately, if you are using GitHub Pages and building/deploying via the classic Deploy-from-a-branch method, you are limited to the legacy jekyll-paginate plugin, which can only paginate on a single page within your site, specifically on the root level index.html page. Ouch. Since I need the ability to paginate a variety of collections on multiple distinct pages, I needed something else - enter jekyll-paginate-v2.

Jekyll-paginate-v2 seemed to satisfy my requirements, but unfortunately, vanilla GitHub Pages does not support many plugins, this being one of them. Honestly, at this point I flirted with the idea of re-writing my entire blog using something like 11ty. I still might do this one day, but really I just want my current site to work the way I want, without having to scrap it. So… the other solution is changing the way I build & deploy the site. Fortunately, GitHub gives you the option to switch from the classic Pages Deploy-from-a-branch method to one based on GitHub Actions. I’m not going to explain what Actions is, but in the context of this writeup, just know it lets me use the full suite of Jekyll plugins, not just the limited selection that GitHub Pages supports. Woo!

Configuring GitHub Actions for GitHub Pages Build & Deploy

Alright. So how can I switch from the classic build+deploy method to the Actions-based one? Jekyll actually has a doc for exactly this. The steps as written are very simple and very straight-forward, or so I thought…

After creating the new action, Pages tried to build my site but quickly ran into an error with the Ruby setup. The traceback ended as follows…

In Gemfile:
  github-pages was resolved to 209, which depends on
    jekyll-mentions was resolved to 1.6.0, which depends on
      html-pipeline was resolved to 2.14.0, which depends on
        nokogiri
Error: The process '/opt/hostedtoolcache/Ruby/3.1.6/x64/bin/bundle' failed with exit code 5

I’ll admit, I was pretty stumped at first, and didn’t really know how to troubleshoot. You see, even though I’ve been running this Jekyll site for over 5 years, I really haven’t learned much about Ruby, hah! Looks like that needed to change, so I persist. After browsing around, I started to home in on the root problem - it seemed like I had some dependency issues stemming from my Gemfile.lock and/or Gemfile files. Ok, nice! Let’s add Gemfile.lock to my .gitignore and then swap out gem 'github-pages' to gem "jekyll", "~> 4.2" in my Gemfile. Commit the repo and then about a minute later, a green check in my workflow record! It worked!

Update: Another troubleshooting thing to consider is whether the ruby-version in the jekyll.yml file is up-to-date.

Configuring Jekyll-Paginate-V2

OK, now that my site can successfully be built & deployed via GitHub Actions, I need to get the jekyll-paginate-v2 plugin enabled and setup. Ultimately, it’s pretty simple to get set up and surprisingly backwards-compatible (with the classic jekyll-paginate plugin), but there’s a few bugs and other little tidbits to know so you don’t pull your hair out…

Caching Bug

The most insidious bug with getting the Jekyll-Paginate-V2 plugin to work is the “caching bug”. This is described here - Resolve the Jekyll Paginate V2 Caching Bug. To fix, go to your Gemfile and replace gem 'jekyll-paginate-v2' to either…

gem 'jekyll-paginate-v2', git: 'https://github.com/mohkale/jekyll-paginate-v2.git', branch: 'liquid-cache-bypass'

or

gem 'jekyll-paginate-v2', git: 'https://github.com/jameshi16/jekyll-paginate-v2.git', branch: 'cache-mismatch-error'

Fixed!

Set indexpage & extension to nil

The second (much more minor) issue I encountered was with how the plugin wants to tack on ‘index.html’ as the default name for generated pages. This causes weird, often just unsightly issues with how the page urls look for paginated pages. You’ll get pages that look like ‘https://domain/paginatedpage/page/3/index.html’, instead of the cleaner ‘https://domain/paginatedpage/page/3’. You can fix this by going into your config.yml file and making sure indexpage and extension are set to nil (as shown below).

  # Optional, the default file extension for generated pages (e.g html, json, xml).
  # Internally this is set to html by default
  extension: 

  # Optional, the default name of the index file for generated pages (e.g. 'index.html')
  # Without file extension
  indexpage: 
Understanding the V2 Pagination Generator

The last consideration is less of a bug and more of just a need-to-know. From v1 to v2 pagination links and paginator object variables/methods are largely the same, so much of any existing pagination code/syntax you may have from using the classic jekyll-paginate plugin should continue to work. To enable pagination on other pages, specify the collection you want to load in via the pages front matter, and continue to use the paginator.posts directive (even though you might be using a non-‘post’ collection).

Here’s more information on the v2 Pagination Generator.


So at the end of the day, I figured it out! As a result, I’ve been able to paginate my Notes, my Activity page and my Changelog, while keeping my main page pagination of my blog posts the same. From here, I still want to add pagination for a few other, less-pressing pages, i.e. the Captain’s Log, Podcasts, Tags, Devlog and more!

Appendix

If you want to replicate my paginator trail, the code I use for the links is provided below.

<!-- Pagination links -->

  {% if paginator.page_trail %}
  <ul class="pagination pagination-sm" style="display: flex; justify-content:center;">
    {% if paginator.previous_page %}
      <li><a href="{{ paginator.previous_page_path | prepend: site.baseurl | replace: '//', '/' }}">&laquo;</a></li>
      <li>&nbsp;<a href="/YOURPAGEHERE">First</a></li>&nbsp;&nbsp;
    {% else %}
      <li class="disabled"><span aria-hidden="true">&laquo;</span></li>
      <li class="disabled"><span aria-hidden="true">&nbsp;First</span></li>&nbsp;&nbsp;
    {% endif %}
    {% for trail in paginator.page_trail %}
      <li>
        {% if paginator.page == trail.num %}
          {{ trail.num }}
        {% else %}
          {% if trail.num == 1 %}
            <a href="/YOURPAGEHERE">{{ trail.num }}</a>
          {% else %}
            <a href="/YOURPAGEHERE/page/{{trail.num}}" title="{{trail.title}}">{{ trail.num }}</a>
          {% endif %}
        {% endif %}
      </li>
      &nbsp;&nbsp;
    {% endfor %}
    {% if paginator.next_page %}
      <li><a href="/YOURPAGEHEREy/page/{{ paginator.total_pages }}/">Last</a> ({{paginator.last_page}})&nbsp;</li>
      <li><a href="{{ paginator.next_page_path | prepend: site.baseurl | replace: '//', '/' }}">&raquo;</a></li>
    {% else %}
      <li class="disabled"><span aria-hidden="true">Last&nbsp;</span></li>
      <li class="disabled"><span aria-hidden="true">&raquo;</span></li>
    {% endif %}
  </ul>
  {% endif %}