Paginating document archive pages with custom routes in Sanity
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:
- A page document (e.g.,
resources) with your hero details and metadata - Multiple post documents (e.g.,
article,blogPost, etc.) that you want to paginate
Recommended Pagination Approach
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)
- Only show "Previous" and "Next" buttons
- Store the
lastIdin the URL as a query parameter:/resources?lastId=abc123 - This is the most efficient approach
Option 2: Support Page Numbers (less performant at scale)
- Accept that higher page numbers will be slower
- Use array slicing for simplicity:
[${(page-1)*100}...${page*100}] - The pagination guide notes this becomes slower linearly with page number, but may be acceptable for your use case
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:
- Users expect numbered pagination on content archives
- Most visitors won't navigate past the first few pages anyway
- It's much simpler to implement
- You can always optimize later if needed
If you have thousands of posts and notice performance issues, then switch to sequential navigation with filter-based pagination.
Show original thread3 replies
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.