
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeFor 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:
You'll have two document types:
resources) with your hero details and metadataarticle, blogPost, etc.) that you want to paginateBased 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.
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
}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
}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 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)
lastId in the URL as a query parameter: /resources?lastId=abc123Option 2: Support Page Numbers (less performant at scale)
[${(page-1)*100}...${page*100}]To show total pages, use:
count(*[_type == "article"])This should be relatively fast but watch out with very large datasets.
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.
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store