Pagination
Splitting a big collection into numbered pages with Eleventy's built-in pagination feature.
Live Implementation
This is the real pager from /blog/ — 7 posts, 3 per page. Click a number, it goes to a real page:
How It Works
Eleventy has pagination built in. Add a pagination block to a template’s front matter and point it at any array (usually a collection), and Eleventy runs that template once per chunk, generating a separate page each time.
pagination:
data: collections.posts
size: 3
alias: posts Inside the template, posts is now just the slice of posts for the current page, and a pagination object is available with everything you need to build page links: pagination.pageNumber (0-indexed), pagination.hrefs (every page’s URL, in order), and more. The blog listing on this site uses exactly this to become /blog/, /blog/2/, /blog/3/, and so on, with a computed permalink so page 1 lands at the clean /blog/ URL instead of /blog/1/.
The numbered links above aren’t a mockup. They’re built from the real post count using the same pager macro blog.njk uses, and they go to the real paginated pages.
Folder Structure
src/blog.njk ← pagination front matter + permalink logic
src/_includes/components/pager.njk ← the numbered link list
src/_includes/demo-live/pagination.njk Important Files
src/blog.njk
---
title: "Blog"
description: "Notes on building this site, and on Eleventy in general."
eleventyNavigation:
key: Blog
order: 3
pagination:
data: collections.posts
size: 3
alias: posts
eleventyComputed:
permalink: "{% if pagination.pageNumber == 0 %}/blog/{% else %}/blog/{{ pagination.pageNumber + 1 }}/{% endif %}"
---
{% extends "layouts/base.njk" %}
{% import "components/pager.njk" as pagerC %}
{% block content %}
<div class="wrapper">
<header class="page__header">
<h1 class="page__title">{{ title }}</h1>
<p class="page__lede">{{ description }}</p>
</header>
<ul class="post-list">
{% for post in posts %}
<li class="post-list__item">
<a class="post-list__title" href="{{ post.url }}">{{ post.data.title }}</a>
<time class="post-list__date" datetime="{{ post.date | htmlDateString }}">{{ post.date | readableDate }} · {{ post.date | timeAgo }}</time>
<p class="post-list__desc">{{ post.data.description }}</p>
</li>
{% endfor %}
</ul>
{{ pagerC.pager(pagination.pageNumber, pagination.hrefs) }}
</div>
{% endblock %} src/_includes/components/pager.njk
{% macro pager(currentIndex, hrefs) %}
{% if hrefs.length > 1 %}
<nav class="pager" aria-label="Pagination">
<ol class="pager__list">
<li>
{% if currentIndex > 0 %}
<a class="pager__step" href="{{ hrefs[currentIndex - 1] }}">← Previous</a>
{% endif %}
</li>
{% for href in hrefs %}
<li>
<a href="{{ href }}" {% if loop.index0 == currentIndex %}aria-current="page"{% endif %}>{{ loop.index }}</a>
</li>
{% endfor %}
<li>
{% if currentIndex < hrefs.length - 1 %}
<a class="pager__step" href="{{ hrefs[currentIndex + 1] }}">Next →</a>
{% endif %}
</li>
</ol>
</nav>
{% endif %}
{% endmacro %} Notes
- By default, only the first paginated page gets added to
collections.all, which is why/blog/shows up once in the header nav instead of three times. pagination.hrefsalready has every page’s URL computed for you. There’s no need to build the list of links by hand.- Page size is just a number. Bump
size: 3up or down and the page count adjusts on its own.