Rift Logo RiftJS

Pagination

Rift includes a built-in pagination utility that makes it easy to split large content arrays (such as blog posts, articles, or product lists) into multiple pages. This feature is used directly inside controller files to drive paginated output generation.

Overview

The paginate(array, pageSize) function accepts any array of items and returns an array of page objects. Each page includes metadata such as the index, start offset, previous/next references, and a slice of the items.

How It Works

You call paginate() inside your controller's params() function to divide content into multiple pages. Then, for each page, you call ctx.param({ page }) to register it.

Example: Paginated Blog Listing

Here's how you can paginate a list of posts and generate separate pages with their own URLs:

Blog Controller Example
export async function params(ctx) {
  const posts = ctx.collections.posts.filter(p => p.locale === ctx.locale);
  const pages = paginate(posts, 10);

  for (const page of pages) {
    ctx.param({ page });
  }
}

export function permalink(ctx) {
  const pageNum = ctx.params.page.index;
  return pageNum === 0
    ? `/${ctx.locale}/blog/`
    : `/${ctx.locale}/blog/page/${pageNum + 1}`;
}

This example creates:

  • /en/blog/ – the first page of posts
  • /en/blog/page/2, /en/blog/page/3, etc. – subsequent pages

Inside your template, you can access page.items to render the current chunk, and use page.previous / page.next to create navigation links.

Usage in Templates

In your template (e.g. blog listing), loop through page.items and render each item. You can also conditionally render previous/next navigation using page.previous and page.next.

Nunjucks Template Example
{% for post in page.items %}
  <h2>{{ post.title }}</h2>
  <p>{{ post.summary }}</p>
{% endfor %}

<nav>
  {% if page.previous != null %}
    <a href="/blog/page/{{ page.previous + 1 }}">Previous</a>
  {% endif %}
  {% if page.next != null %}
    <a href="/blog/page/{{ page.next + 1 }}">Next</a>
  {% endif %}
</nav>

Example: Paginate by Category

You can also paginate blog posts for each category:

Category Pagination Controller
export async function params(ctx) {
  const posts = ctx.collections.posts.filter(p => p.locale === ctx.locale);
  const categories = [...new Set(posts.map(p => p.category))];

  for (const category of categories) {
    const postsInCategory = posts.filter(p => p.category === category);
    const pages = paginate(postsInCategory, 10);

    for (const page of pages) {
      ctx.param({ category, page });
    }
  }
}

export function permalink(ctx) {
  const index = ctx.params.page.index;
  const base = `/${ctx.locale}/blog/category/${ctx.params.category}`;
  return index === 0 ? base : `${base}/page/${index + 1}`;
}

This will generate pages like:

  • /en/blog/category/javascript
  • /en/blog/category/javascript/page/2
  • /en/blog/category/css

Usage in Templates

In your template (e.g. blog listing), loop through page.items and render each item. You can also conditionally render previous/next navigation using page.previous and page.next.

Nunjucks Template Example
{% for post in page.items %}
  <h2>{{ post.title }}</h2>
  <p>{{ post.summary }}</p>
{% endfor %}

<nav>
  {% if page.previous != null %}
    <a href="/blog/page/{{ page.previous + 1 }}">Previous</a>
  {% endif %}
  {% if page.next != null %}
    <a href="/blog/page/{{ page.next + 1 }}">Next</a>
  {% endif %}
</nav>

Pagination Window (Page Number Links)

For user-friendly navigation, you can display a pagination window that shows the current page number alongside nearby pages:

In your controller:

Controller with Window
const pages = paginate(posts, 10);

for (let i = 0; i < pages.length; i++) {
  const page = pages[i];
  const window = pages.slice(Math.max(i - 2, 0), i + 3);
  ctx.param({ page, window });
}

In your template:

Windowed Pagination Template
<nav class="pagination">
  {% if page.previous != null %}
    <a href="/blog/page/{{ page.previous + 1 }}">« Previous</a>
  {% endif %}

  {% for p in window %}
    {% set pageNum = p.index + 1 %}
    {% if p.index == page.index %}
      <strong>{{ pageNum }}</strong>
    {% else %}
      <a href="/blog/page/{{ pageNum }}">{{ pageNum }}</a>
    {% endif %}
  {% endfor %}

  {% if page.next != null %}
    <a href="/blog/page/{{ page.next + 1 }}">Next »</a>
  {% endif %}
</nav>

This displays a range like below