109Cafe

Cursor based pagination with sanity

DevelopmentOct 3, 2024

This article helps you implement cursor-based pagination with Sanity. The implementation includes the following features:

1. Support for querying backward to get the cursor for the previous page.

2. Only one cursor is needed for both forward and backward queries.

3. Two GROQ queries are required.

Assume you have a cursor-based GROQ query like this

PostsQuery
*[
 _type == "post"
 && defined(slug) && defined(publishedAt) && hidden != true
 && ($lastPublishedAt == null || publishedAt < $lastPublishedAt)
] {
  publishedAt,
  title,
  'slug': slug.current,
  _id,
  summary,
  cover
} | order(publishedAt desc)[0...$limit]

This is a basic paginated query for posts sorted by publishedAt.

Forward Query

In a typical forward paginated query, you need to find out:

1. If there’s a next page

2. The cursor for the next page

It’s simple to do this: just increase the limit by one.

So the forward query will be:

const forwards = await sanity.fetch(PostsQuery, {
  lastPublishedAt: cursor || null,
  limit: limit + 1
});
const result = forwards.slice(0, limit);
const hasNext = forwards.length > limit;
const nextCursor = hasNext ? result[result.length - 1].publishedAt : null;

Backward Query

If you want to show a button for the previous page, you need to find out:

1. If there’s a previous page

2. The cursor for the previous page

How can you do this? Simply reverse the query order.

PostsBackwardQuery
*[
 _type == "post"
 && defined(slug) && defined(publishedAt) && hidden != true
 && publishedAt > $firstPublishedAt
] {
  _id,
  publishedAt
} | order(publishedAt asc)[0...$limit]

We reverse the query order by changing desc to asc.

const backwards = await sanity.fetch(PostsBackwardQuery, {
  firstPublishedAt: cursor || null,
  limit: limit + 1,
});
const hasPrevious = backwards.length > 0;
const previousCursor = backwards.length > limit ? backwards[backwards.length - 2] : null;

We also request one more item to check if we've reached the first page—usually, we don’t need a cursor for it.