Using Sanity to generate a static blog with pagination
Absolutely! You can use Sanity to generate a static blog with static pagination using either Next.js or Gatsby. Here's how:
Static Pagination with Next.js
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.
Example with Next.js App Router:
// 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>
)
}Better Performance: Filter-Based Pagination
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
}Gatsby Static Pagination
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,
},
})
})
}Key Considerations
Performance: For datasets with thousands of posts, use filter-based pagination with
_idsorting instead of array slicing for much better build performanceIncremental 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 – 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.