How to Configure Sanity CMS in a Next.js Project

Sanity CMS and Next.js are a match made in developer heaven. Sanity provides a flexible, real-time headless content platform with a powerful query language (GROQ), while Next.js delivers a production-grade React framework with hybrid rendering capabilities. Together, they form a robust stack for building fast, content-driven web applications — from marketing sites and blogs to e-commerce storefronts and documentation portals.
In this guide, you will walk through the complete setup process: from creating a Sanity project and installing the client, to fetching content with GROQ queries inside Next.js pages and optionally embedding Sanity Studio directly in your app.
Prerequisites
Before getting started, make sure you have the following in place:
- Node.js v18 or later installed on your machine
- npm (v9+) or yarn as your package manager
- A free Sanity account — sign up at sanity.io
- An existing Next.js project (v13 or later recommended) or a fresh one created with create-next-app
Step 1 – Creating a New Sanity Project
The fastest way to scaffold a new Sanity project is via the official CLI. Run the following command in your terminal:
npm create sanity@latestThe CLI will guide you through an interactive setup. You will be prompted to:
- Log in to or create your Sanity account
- Choose or create a Sanity organization
- Name your project and select a dataset (default: production)
- Choose a project template (e.g., Blog, E-commerce, or Clean)
Once the setup completes, note your Project ID and Dataset name — you will need these in the next steps. You can always find them in your Sanity project dashboard at manage.sanity.io.
Step 2 – Installing the Sanity Client in Next.js
Navigate to your Next.js project directory and install the official Sanity JavaScript client:
npm install @sanity/clientThis package provides the core client for communicating with the Sanity Content Lake API. It supports both browser and Node.js environments, making it compatible with all Next.js rendering strategies — SSR, SSG, ISR, and React Server Components.
Step 3 – Setting Up Environment Variables
Create a .env.local file in the root of your Next.js project and add the following environment variables:
# .env.local
NEXT_PUBLIC_SANITY_PROJECT_ID=your_project_id_here
NEXT_PUBLIC_SANITY_DATASET=production
NEXT_PUBLIC_SANITY_API_VERSION=2024-01-01A few important notes on these variables:
NEXT_PUBLIC_SANITY_PROJECT_ID: Your unique Sanity project identifier, found in manage.sanity.io.NEXT_PUBLIC_SANITY_DATASET: The dataset to query. Defaults toproductionfor most projects.NEXT_PUBLIC_SANITY_API_VERSION: The API version date. Using a fixed date ensures your queries remain stable even as the Sanity API evolves. Use today's date or a recent stable date.
The NEXT_PUBLIC_ prefix exposes these variables to the browser bundle. If you are only querying Sanity server-side (e.g., in Server Components or API routes), you can omit this prefix for added security.
Step 4 – Creating the Sanity Client Configuration File
Create a dedicated client configuration file. A common convention is to place it at lib/sanity.js (or sanity.js at the project root):
// lib/sanity.js
import { createClient } from '@sanity/client';
export const sanityClient = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
apiVersion: process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2024-01-01',
useCdn: process.env.NODE_ENV === 'production', // Use CDN in production for faster reads
});If you are using TypeScript, you can type the client for better IDE support:
// lib/sanity.ts
import { createClient, SanityClient } from '@sanity/client';
export const sanityClient: SanityClient = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,
apiVersion: process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2024-01-01',
useCdn: process.env.NODE_ENV === 'production',
});The useCdn flag enables Sanity's global CDN for read requests in production, significantly reducing latency. Set it to false when you need fresh, uncached data (e.g., for preview mode or real-time updates).
Step 5 – Fetching Data with GROQ Queries
GROQ (Graph-Relational Object Queries) is Sanity's powerful query language. It is expressive, concise, and purpose-built for querying structured content. Below are examples for both the Pages Router and the App Router.
Pages Router – getStaticProps
// pages/blog/index.js
import { sanityClient } from '../../lib/sanity';
export async function getStaticProps() {
const query = `*[_type == "post"] | order(publishedAt desc) {
_id,
title,
slug,
publishedAt,
"excerpt": array::join(string::split(pt::text(body), "")[0..200], ""),
mainImage {
asset->{ url }
}
}`;
const posts = await sanityClient.fetch(query);
return {
props: { posts },
revalidate: 60, // ISR: revalidate every 60 seconds
};
}
export default function BlogIndex({ posts }) {
return (
<main>
<h1>Blog</h1>
<ul>
{posts.map((post) => (
<li key={post._id}>
<a href={`/blog/${post.slug.current}`}>{post.title}</a>
</li>
))}
</ul>
</main>
);
}App Router – React Server Component
// app/blog/page.js
import { sanityClient } from '../../lib/sanity';
const query = `*[_type == "post"] | order(publishedAt desc) {
_id,
title,
slug,
publishedAt
}`;
export default async function BlogPage() {
const posts = await sanityClient.fetch(query, {}, {
next: { revalidate: 60 }, // Next.js cache revalidation
});
return (
<main>
<h1>Blog</h1>
<ul>
{posts.map((post) => (
<li key={post._id}>
<a href={`/blog/${post.slug.current}`}>{post.title}</a>
</li>
))}
</ul>
</main>
);
}GROQ queries follow the pattern *[_type == "documentType"] to filter documents by type, followed by a projection { field1, field2 } to select only the fields you need. This keeps API responses lean and efficient.
Step 6 – Embedding Sanity Studio in Next.js (Optional)
The next-sanity package allows you to embed Sanity Studio directly inside your Next.js application, making it accessible at a route like /studio. This is ideal for keeping your content editing experience co-located with your frontend.
First, install the package:
npm install next-sanityNext, create a catch-all route for the Studio. In the App Router, create the file app/studio/[[...tool]]/page.jsx:
// app/studio/[[...tool]]/page.jsx
'use client';
import { NextStudio } from 'next-sanity/studio';
import config from '../../../sanity.config'; // Your Sanity config file
export default function StudioPage() {
return <NextStudio config={config} />;
}You will also need a sanity.config.js file at the project root that defines your workspace:
// sanity.config.js
import { defineConfig } from 'sanity';
import { structureTool } from 'sanity/structure';
import { visionTool } from '@sanity/vision';
export default defineConfig({
name: 'default',
title: 'My Next.js Blog',
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
plugins: [
structureTool(),
visionTool(), // Enables the GROQ query playground inside Studio
],
schema: {
types: [], // Import and register your schema types here
},
});After this setup, navigating to http://localhost:3000/studio will render the full Sanity Studio interface within your Next.js app.
Tip – Handling Images with @sanity/image-url
Sanity stores images as asset references, not plain URLs. The @sanity/image-url package makes it easy to generate optimized image URLs with transformations like resizing, cropping, and format conversion.
Install the package:
npm install @sanity/image-urlCreate a helper utility for building image URLs:
// lib/sanityImage.js
import imageUrlBuilder from '@sanity/image-url';
import { sanityClient } from './sanity';
const builder = imageUrlBuilder(sanityClient);
export function urlFor(source) {
return builder.image(source);
}Use it in your components to generate responsive, optimized image URLs:
// components/PostCard.js
import { urlFor } from '../lib/sanityImage';
import Image from 'next/image';
export default function PostCard({ post }) {
return (
<article>
{post.mainImage && (
<Image
src={urlFor(post.mainImage).width(800).height(450).format('webp').url()}
alt={post.title}
width={800}
height={450}
/>
)}
<h2>{post.title}</h2>
</article>
);
}The builder supports a rich set of transformations: .width(), .height(), .fit(), .format(), .quality(), and more — all processed by Sanity's image pipeline on the fly.
Conclusion
You have now set up a fully functional Sanity CMS integration with Next.js — from project creation and client configuration to data fetching with GROQ and image optimization. This foundation is production-ready and scales well as your content model grows.
From here, consider exploring the following to take your integration further:
- Sanity's Live Preview and Draft Mode for real-time content editing
- Portable Text rendering with @portabletext/react for rich body content
- Webhooks and on-demand ISR for instant content updates without full rebuilds
- Custom schema types and input components to tailor the Studio to your team's workflow
The Sanity and Next.js ecosystem is rich and actively maintained. The official documentation at sanity.io/docs and nextjs.org/docs are excellent resources to continue your journey.
