Tech3 min read

Crafting a Headless CMS with Sanity & Next.js

EP

Ezeikel Pemberton

June 22, 2026

Crafting a Headless CMS with Sanity & Next.js

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/cli

Now, 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 --typescript

Navigate into your project directory:

cd my-nextjs-app

Step 4: Connecting Next.js to Sanity

Install the necessary Sanity client:

npm install @sanity/client

Create 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

Tech

Share this article

Enjoyed this article?

Subscribe to get notified when I publish new posts about building products, coding, and indie hacking.

Subscribe to newsletter