Paginating document archive pages with custom routes in Sanity

3 repliesLast updated: Nov 30, 2025

looking for help with setting up a “page of posts” … its a document type archive page, but it exists as a page itself (in order to get hero details, etc) and then i want to query for all the documents of the type… the area i’m struggling with is paginating it, so something like
/resources/page/2
etc
AI Update

For setting up a paginated "page of posts" where the page itself is a document (with hero details, etc.) and you want to paginate through posts at URLs like /resources/page/2, here's how to approach it:

The Setup

You'll have two document types:

Based on the official pagination guide, avoid using array slicing like [100...200] for pagination as it becomes inefficient with large datasets. Instead, use filter-based pagination.

Filter-Based Pagination

For the first page (/resources or /resources/page/1):

*[_type == "article"] | order(_id) [0...100] {
  _id, title, body, slug
}

For subsequent pages (/resources/page/2, etc.), use filtering with the last document's ID:

*[_type == "article" && _id > $lastId] | order(_id) [0...100] {
  _id, title, body, slug
}

Sorting by Other Fields (like publishedAt)

If you want to sort by a non-unique field like publishedAt, you need a tiebreaker to avoid skipping documents:

*[_type == "article" && (
  publishedAt > $lastPublishedAt
  || (publishedAt == $lastPublishedAt && _id > $lastId)
)] | order(publishedAt) [0...100] {
  _id, title, body, publishedAt
}

Implementation Example

Here's how you might structure your Next.js route:

// app/resources/[[...page]]/page.tsx
export default async function ResourcesPage({ params }) {
  const pageNum = params.page?.[1] ? parseInt(params.page[1]) : 1;
  
  // Fetch the page document for hero details
  const pageData = await client.fetch(`
    *[_type == "resourcesPage"][0] {
      title, hero, description
    }
  `);
  
  // For pagination, you'll need to track lastId
  // This is tricky with page numbers - see below
  const posts = await client.fetch(`
    *[_type == "article"] | order(_id) [${(pageNum - 1) * 100}...${pageNum * 100}] {
      _id, title, slug
    }
  `);
  
  return (
    <div>
      <Hero {...pageData.hero} />
      <PostList posts={posts} currentPage={pageNum} />
    </div>
  );
}

The Page Number Challenge

The filter-based approach doesn't support random access to page numbers easily. To jump to page 5, you'd need to know the lastId from page 4. You have two options:

Option 1: Sequential Navigation Only (most performant)

Option 2: Support Page Numbers (less performant at scale)

Getting Total Count

To show total pages, use:

count(*[_type == "article"])

This should be relatively fast but watch out with very large datasets.

My Recommendation

For a public-facing "resources" page, I'd suggest Option 2 with array slicing despite the performance tradeoff, because:

If you have thousands of posts and notice performance issues, then switch to sequential navigation with filter-based pagination.

Show original thread
3 replies

Was this answer helpful?

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Related contributions