
A practical guide to setting up Sanity CMS with Next.js, covering local development, schema definition, data fetching, secure revalidation with webhooks, and production deployment. Learn how to keep CMS content in sync with a statically generated or server-rendered Next.js app.
Integrating Sanity CMS with Next.js: From Setup to Production
Using Sanity CMS with Next.js is a powerful way to manage content for your applications. While the official documentation provides a quickstart guide, there are a few practical tips and patterns that make development, revalidation, and deployment smoother. In this guide, we’ll walk through setting up Sanity inside a Next.js project, defining schemas, fetching data, handling revalidation, and deploying to production.
1. Setting Up Your Next.js Project with Bun
For modern development, I prefer using Bun for Next.js projects. You can create a new Next.js project using:
bun create next-app@latest my-app --yes
cd my-app
bun devThis gives you a working Next.js project running on Bun, ready for CMS integration.
2. Adding Sanity CMS Inside the Next.js Project
I like to keep the Sanity studio inside the Next.js project root. This keeps everything in one repository and simplifies local development and deployments. Typically, I name the folder:
/cmsThen, follow the standard Sanity setup:
Select your project type, dataset, and confirm your preferences. Now your Sanity studio lives inside the Next.js project, but is isolated in the cms folder.
Keeping Sanity inside the Next.js project allows easier version control, shared environment variables, and simplified local development.
3. Defining Sanity Schemas
Once your studio is set up, you can define your content models. For example, if you want to manage blog posts:
export default {
name: 'blog',
title: 'Blog Post',
type: 'document',
fields: [
{ name: 'title', type: 'string', title: 'Title' },
{ name: 'slug', type: 'slug', title: 'Slug', options: { source: 'title' } },
{ name: 'content', type: 'array', title: 'Content', of: [{ type: 'block' }] },
{ name: 'publishedAt', type: 'datetime', title: 'Published At' },
],
}Add it to the schema index:
import { blog } from'./blog'
export default {
types: [blog],
}This creates a structured CMS that your Next.js app can query.
4. Fetching Sanity Data in Next.js
With your schemas in place, you can query content from Sanity using the official client:
import { createClient } from'next-sanity'
export const client = createClient({
projectId: process.env.SANITY_PROJECT_ID,
dataset: process.env.SANITY_DATASET,
apiVersion: '2023-01-01',
useCdn: false,// set to true for static builds
})Then fetch data in a page:
import { client } from'@/lib/sanityClient'
export async function getStaticProps() {
const blogs = await client.fetch(`*[_type == "blog"]`)
return {props: { blogs },revalidate:60 }
}5. Handling Revalidation Between Local Dev and Production
One common point of confusion is why published content in Sanity doesn’t appear on the deployed site immediately. This is due to SSG/SSR caching.
To handle this properly, there are a few required steps.
1. Add Production Domain to Sanity CORS Settings
In the Sanity dashboard:
- Go to Settings → API → CORS Origins
- Add your deployed Next.js domain
- Allow credentials if needed
Without this, production requests from your Next.js app will fail.
2. Create a Secure Revalidation API Route in Next.js
Instead of sending a secret as a query parameter, the correct approach is to use Sanity’s built-in webhook secret, which is sent in the request headers.
Example revalidation route:
import { revalidatePath } from 'next/cache'
import { NextRequest } from 'next/server'
export async function POST(request: NextRequest) {
const secret = request.headers.get('sanity-webhook-secret')
if (secret !== process.env.REVALIDATE_SECRET) {
returnResponse.json({message: 'Invalid token' }, {status: 401 })
}
revalidatePath('/')
revalidatePath('/blogs')
revalidatePath('/blogs/[slug]', 'page')
revalidatePath('/projects')
revalidatePath('/projects/[slug]', 'page')
returnResponse.json({ revalidated: true })
}The secret is stored safely in environment variables and never exposed in URLs.
3. Configure the Webhook in Sanity Studio
In the Sanity dashboard:
- Go to Settings → Webhooks
- Create a new webhook
- Set the URL to:
https://www.yoursite.com/api/revalidate- Use the Secret field provided by Sanity
- Use the same value as
REVALIDATE_SECRETin your Next.js environment variables - Trigger on create, update, and delete
Sanity automatically sends this secret in the request headers.
Why This Approach Is Important
Using the webhook secret field instead of query parameters:
- Prevents accidental exposure of secrets
- Avoids revalidation abuse or spam
- Protects you from unnecessary Vercel revalidation costs
- Follows Sanity’s recommended security model
Any public revalidation endpoint must be protected. Otherwise, anyone could trigger revalidation repeatedly and cause performance or billing issues.
6. Deploying Next.js and Sanity
When deploying:
- Ignore the Sanity studio in Vercel
In your Next.js root, create a .vercelignore file:
cmsThis prevents Vercel from trying to build the studio as part of the Next.js project.
- Deploy the Next.js app
Push to GitHub and deploy using Vercel as usual.
- Deploy the Sanity studio
From the cms folder:
cd cms
sanity deployThis deploys the CMS separately while keeping it inside your repository.
7. Key Takeaways
- Keep Sanity inside your Next.js repo for simplicity (
/cms). - Use feature-based schemas to keep content organized.
- Handle revalidation carefully to ensure published content appears immediately on production.
- Protect your revalidation endpoint with a secret key to avoid abuse.
- Ignore Sanity in Vercel builds and deploy it separately.
Following this workflow ensures smooth development, accurate production content, and maintainable CMS integration.