Questions about using Sanity.io with Next.js directory structure
You're right that the documentation has changed! Next.js App Router (introduced in Next.js 13+) completely replaced the need for getStaticProps and getServerSideProps with a simpler, more powerful approach using React Server Components. Many people are successfully using Sanity with Next.js—it's actually one of the most popular stacks for content-driven applications.
The Modern Approach: React Server Components
With the App Router (the app/ directory), you fetch data directly inside your components. No special functions needed! Here's how it works:
// app/blog/page.tsx
import { createClient } from 'next-sanity'
const client = createClient({
projectId: 'your-project-id',
dataset: 'production',
apiVersion: '2024-01-01',
useCdn: true,
})
// Control caching behavior
export const revalidate = 60 // Revalidate every 60 seconds
export default async function BlogPage() {
// Fetch data directly in the component
const posts = await client.fetch(`*[_type == "post"]`)
return (
<div>
{posts.map(post => (
<article key={post._id}>{post.title}</article>
))}
</div>
)
}Setting Up next-sanity with App Router
- Install the package:
npm install next-sanity @sanity/image-url- Create a Sanity client (typically in
lib/sanity.ts):
import { createClient } from 'next-sanity'
export const client = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,
apiVersion: '2024-01-01',
useCdn: true, // Use CDN for faster reads in production
})Cache Control Options
The App Router gives you several ways to control caching, replacing the old getStaticProps/getServerSideProps patterns:
Time-based revalidation (like ISR):
export const revalidate = 3600 // Revalidate every hourForce dynamic rendering (like getServerSideProps):
export const dynamic = 'force-dynamic'No caching for specific fetches:
const posts = await client.fetch(query, {}, { cache: 'no-store' })Generating Static Paths
For dynamic routes, use generateStaticParams instead of getStaticPaths:
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await client.fetch(`*[_type == "post"]{ "slug": slug.current }`)
return posts.map((post) => ({
slug: post.slug,
}))
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await client.fetch(
`*[_type == "post" && slug.current == $slug][0]`,
{ slug: params.slug }
)
return <article>{post.title}</article>
}Important: Next.js 15 Breaking Change
If you're using Next.js 15, params are now a Promise, so you need to await them:
export default async function BlogPost({
params
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params // Must await!
const post = await client.fetch(...)
return <article>{post.title}</article>
}Additional Resources
The next-sanity toolkit provides everything you need for building production-grade Next.js applications with Sanity, including utilities for live content updates and visual editing. There's also a comprehensive App Router course that walks through fetching Sanity content step-by-step.
The pattern is actually simpler once you understand that components themselves are async and can fetch data directly—no need for the old data-fetching functions!
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.