Crafting a Headless CMS with Sanity & Next.js
Ezeikel Pemberton
June 22, 2026

Photo: AI Generated
Hey there, fellow tech enthusiasts! Today, I’m diving into one of my favorite topics: building a headless CMS using Sanity.io paired with Next.js. If you're a software developer, indie hacker, or content creator curious about the power of modern web technologies, you’re in for a treat. We'll explore the steps, the code, and the reasoning behind using Sanity and Next.js to build a flexible and robust content management system.
Why Headless CMS?
Before we jump into the nitty-gritty, let's quickly discuss why you might want to go headless. A headless CMS decouples the backend (where content is managed) from the frontend (where content is displayed). This separation provides:
- Flexibility: Use any frontend technology to present your content.
- Scalability: Easily manage and scale content across multiple platforms.
- Performance: Optimize delivery with modern frontend frameworks.
Getting Started with Sanity and Next.js
Sanity.io is a highly customizable and performant backend for your CMS needs, while Next.js is a powerful React-based framework for building web applications. Together, they make a killer combo for any modern web project.
Step 1: Setting Up Sanity
First, you’ll need to set up a Sanity project. If you haven’t already, install the Sanity CLI:
npm install -g @sanity/cliNow, create a new Sanity project:
sanity init --project <your-project-name>Follow the CLI prompts to configure your project. This will set up a new Sanity studio where you can manage your content models and data.
Step 2: Defining Your Content Schema
Sanity is schema-driven, which means you define your content types in JavaScript. Here's an example schema for a blog post:
export default {
name: 'post',
title: 'Post',
type: 'document',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
},
{
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
maxLength: 96,
},
},
{
name: 'content',
title: 'Content',
type: 'array',
of: [{ type: 'block' }],
},
],
};Add this schema to your Sanity project in the schemas directory.
Step 3: Setting Up Next.js
With Sanity configured, let's set up a Next.js project. If you haven't already, create a new Next.js app:
npx create-next-app@latest my-nextjs-app --typescriptNavigate into your project directory:
cd my-nextjs-appStep 4: Connecting Next.js to Sanity
Install the necessary Sanity client:
npm install @sanity/clientCreate a sanity-client.ts utility file to manage the connection:
import { createClient } from '@sanity/client';
const sanityClient = createClient({
projectId: '<your-project-id>',
dataset: 'production',
useCdn: true, // `false` if you want to ensure fresh data
apiVersion: '2023-10-01',
});
export default sanityClient;Step 5: Fetching Data in Next.js
Let's fetch and display a list of blog posts. Create a new page component, BlogPosts.tsx:
import sanityClient from '../sanity-client';
type Post = {
_id: string;
title: string;
slug: { current: string };
};
const BlogPosts = ({ posts }: { posts: Post[] }) => {
return (
<div>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post._id}>
<a href={`/post/${post.slug.current}`}>{post.title}</a>
</li>
))}
</ul>
</div>
);
};
export async function getStaticProps() {
const posts: Post[] = await sanityClient.fetch(`*[_type == "post"]{
_id,
title,
slug
}`);
return {
props: { posts },
};
}
export default BlogPosts;Step 6: Dynamic Routing with Next.js
To create dynamic routes for each blog post, use Next.js's dynamic routing. Create a new file [slug].tsx in the pages/post directory:
import { GetStaticPaths, GetStaticProps } from 'next';
import sanityClient from '../../sanity-client';
type Post = {
title: string;
content: any[];
};
const Post = ({ post }: { post: Post }) => {
return (
<div>
<h1>{post.title}</h1>
<div>{/* Render content blocks here */}</div>
</div>
);
};
export const getStaticPaths: GetStaticPaths = async () => {
const paths = await sanityClient.fetch(`*[_type == "post"]{
"params": { "slug": slug.current }
}`);
return {
paths,
fallback: false,
};
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const post = await sanityClient.fetch(`*[_type == "post" && slug.current == $slug][0]`, {
slug: params?.slug,
});
return {
props: { post },
};
};
export default Post;Wrapping Up
And there you have it—a functional headless CMS with Sanity and Next.js! This setup enables you to manage content with Sanity's rich interface and deliver it using the powerful Next.js framework. As you expand your project, consider:
- Styling: Integrate a CSS-in-JS solution like styled-components or Tailwind CSS.
- Rich Content: Use Sanity’s portable text to handle complex content structures.
- Deployment: Deploy your Next.js app on platforms like Vercel for seamless scaling.
Building a headless CMS might seem daunting initially, but once you get the hang of it, the possibilities are endless. Whether you're creating a blog, portfolio, or e-commerce site, this stack offers flexibility and performance that is hard to beat.
Happy coding! If you have any questions or insights, feel free to drop a comment below. Let's keep building awesome things together!
---
Feel free to reach out if you need more tailored advice on your specific project. I'm here to help fellow builders and creators like you succeed!
Tags
Enjoyed this article?
Subscribe to get notified when I publish new posts about building products, coding, and indie hacking.
Subscribe to newsletter