
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeAbsolutely! You can use Sanity to generate a static blog with static pagination using either Next.js or Gatsby. Here's how:
For Next.js, you'll use generateStaticParams (App Router) or getStaticPaths (Pages Router) to pre-generate paginated pages at build time. The key is to use efficient GROQ pagination techniques to fetch your content.
// app/blog/[page]/page.tsx
import { sanityFetch } from '@/sanity/lib/client'
import { defineQuery } from 'next-sanity'
const POSTS_PER_PAGE = 10
// Generate static pages for each pagination page
export async function generateStaticParams() {
const totalPosts = await sanityFetch({
query: defineQuery(`count(*[_type == "post"])`),
})
const totalPages = Math.ceil(totalPosts / POSTS_PER_PAGE)
return Array.from({ length: totalPages }, (_, i) => ({
page: (i + 1).toString(),
}))
}
// Fetch posts for each page
export default async function BlogPage({ params }: { params: { page: string } }) {
const pageNum = parseInt(params.page)
const start = (pageNum - 1) * POSTS_PER_PAGE
const posts = await sanityFetch({
query: defineQuery(`
*[_type == "post"]
| order(publishedAt desc)
[${start}...${start + POSTS_PER_PAGE}] {
_id,
title,
slug,
publishedAt,
excerpt
}
`),
})
return (
<div>
{posts.map(post => (
<article key={post._id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}For better performance, especially with large datasets, use filter-based pagination instead of array slicing. This approach is much more efficient:
export async function generateStaticParams() {
const POSTS_PER_PAGE = 10
let lastId = ''
const pages = []
let pageNum = 1
while (true) {
const posts = await sanityFetch({
query: defineQuery(`
*[_type == "post" && _id > $lastId]
| order(_id)
[0...${POSTS_PER_PAGE}] {
_id
}
`),
params: { lastId },
})
if (posts.length === 0) break
pages.push({ page: pageNum.toString() })
lastId = posts[posts.length - 1]._id
pageNum++
}
return pages
}For Gatsby, you'll use the createPages API in gatsby-node.js:
const path = require('path')
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const POSTS_PER_PAGE = 10
// Query total post count from Sanity
const result = await graphql(`
{
allSanityPost(sort: { publishedAt: DESC }) {
totalCount
nodes {
_id
slug {
current
}
}
}
}
`)
const posts = result.data.allSanityPost.nodes
const totalPages = Math.ceil(result.data.allSanityPost.totalCount / POSTS_PER_PAGE)
// Create paginated blog list pages
Array.from({ length: totalPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/blog` : `/blog/${i + 1}`,
component: path.resolve('./src/templates/blog-list.js'),
context: {
limit: POSTS_PER_PAGE,
skip: i * POSTS_PER_PAGE,
currentPage: i + 1,
totalPages,
},
})
})
}Performance: For datasets with thousands of posts, use filter-based pagination with _id sorting instead of array slicing for much better build performance
Incremental Static Regeneration (ISR): With Next.js, you can use ISR to update pages after build time without full rebuilds
sanityFetch: Use this helper in Next.js for automatic caching and revalidation configuration
Build Time: Consider the total number of pages you're generating - too many can slow down builds significantly
Both approaches work great with Sanity! Next.js with App Router gives you more modern features and flexibility, while Gatsby remains a solid choice for traditional static sites.
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