Adebiyi Adedotun Caught in the web, breaking things and learning fast.

Content management in Next.js with Sanity CMS

6 min read 1739

Content Management In Next.js With Sanity CMS

Next.js is a React framework that aims to extend the capabilities of React with built-in features like routing, TypeScript support, and hybrid static and server rendering for websites and apps.

While content in a Next.js app can be hardcoded, read from a JSON file, or consumed from a database, sourcing content from a content management system (CMS) like Sanity CMS can provide more flexible authoring to developers.

In this post, we’ll take a look at how using Next.js and Sanity CMS, a headless CMS, can provide better content management in a Jamstack-powered web application or website.

What is Sanity CMS?

Sanity CMS is a headless CMS, which is a content authoring backend system decoupled from the presentation layer or client. It exposes stored content as data over an API for consumption on different platforms such as websites, mobile applications, and the Internet of Things, and is a stark divergence from traditional CMS like WordPress.

Sanity CMS takes a structured approach to content authoring, providing a concise number of features to manage images through its image pipeline, text through Portable Text, and design. It also offers Sanity Studio, a fully functional, customizable, and extendable editor built with React.

Benefits of a headless CMS

Headless CMS dutifully embodies the decoupled approach of the Jamstack architecture with benefits like the following:


Because the backend system is separated from the presentation layer, there is a smaller area exposed to security threats.

Data reusability

Headless content is exposed as data over an API and is thus platform agnostic.

Structured content

Content modeling is at the heart of headless CMS and requires content descriptions be a first-class citizen, meaning they can be structured for the specific needs of any app.

Easier editing

The separation between content and code makes it easier for content editors to focus on content editing and developers to focus on code.

Developer experience

In the headless CMS ecosystem, known as the head, is where content is consumed in the head. It is irrelevant to the body, where content is authored and stored, and can be picked at a developer’s discretion.

Improved build times

Jamstack sites have been known to suffer from long build times when they contain a considerably large amount of content. While there are solutions like Incremental Static Regeneration (ISR), incremental builds, and Distributed Persistent Rendering (DPR), separating content from the head solves the long build time problem within headless CMS.

Managing content with Sanity CMS

To illustrate using Next.js with Sanity CMS in the Jamstack architecture, we’ll build a simple landing page to understand Sanity’s basic content management process, including working with its command-line interface (CLI) and manipulating its schemas, within Next.js.

Follow along with this commit that includes styling and default landing page content that is hardcoded. Our final product will look like the image below:

Next.js And Sanity CMS Final Project Page

The method to our madness will be to:

  1. Create and publish content on Sanity Studio
  2. Fetch the headless content as data in our Next.js app for dynamic content

Create and run a Next.js app

First, create a Next.js app with its content hardcoded.

Begin by creating a Next.js app with create-next-app and run this command:

yarn create next-app client

Next, run the Next.js app with the following command:

cd client
yarn dev

Visit the Next.js app at http://localhost:3000.

Default Next.js App running On http://localhost:3000
Default Next.js App running on http://localhost:3000.

Now that we’ve successfully created a Next.js app with hardcoded content, it’s time to create and run a project in Sanity Studio.

Create and run a Sanity Studio project

Before proceeding to the next step, make sure you have a Sanity account or create one if you do not.

Next, install the Sanity CLI globally with the following command:

yarn add @sanity/cli --global

This will install the necessary tooling for working with Sanity through the CLI.

After the Sanity CLI installs, create a new Sanity project with the following:

sanity init

When this command runs, an interactive question-and-answer session appears for creating a Sanity Studio project. We can input the following answers to the corresponding questions for this project:

  • For Select project to use input Create new project
  • For Your project name: input cms
  • For Use the default dataset configuration? input Yes
  • For Select project template input Clean project with no predefined schemas
Answers to the question and answer prompt when setting up a Sanity Studio project
Answers to the question and answer prompt when setting up a Sanity Studio project

While we created a Sanity Studio project with Sanity CLI, we can alternatively use one of the starters as well.

To run the Sanity project, start Sanity Studio on port 3333 with the following command:

cd cms
sanity start -p 3333

Then visit the Next.js app at http://localhost:3333 and sign in to if prompted.

Visiting the Next.js app page

We currently have an empty schema and it must be updated.

Editing the schemas

Schemas are at the core of structured content modeling in and refer to the field types that a document is made up of, such as document, image, object, or reference. These types design the content on with metadata properties like name, type, and description.

We can follow along with this commit to add the required schemas.

The landing page needs two schemas, so we’ll create schemas homepage.js and siteheader.js in the schemas directory at the root of the project.

Update schemas/homepage.js with the following:

// sanity/schemas/homepage.js
export default {
  name: 'homepage',
  title: 'Homepage',
  type: 'document',
  fields: [
      name: 'title',
      title: 'Homepage title',
      type: 'string',
      description: "What's the title of the homepage hero?",
      name: 'subtitle',
      title: 'Homepage subtitle',
      type: 'string',
      description: "What's the subtitle of the homepage hero?",
      name: 'image',
      title: 'Homepage image',
      type: 'image',
      name: 'cta',
      description: "What's URL for the homepage CTA?",
      title: 'CTA',
      type: 'slug',
      options: {
        maxLength: 200,
      validation: (Rule) => [Rule.required().error('Field cannot be empty')],

We’ve now created a document schema with the fields title, subtitle, image, and call-to-action (CTA). The CTA field also includes a validation function that ensures the field cannot be empty.

We can see the repeating pattern of name, title, and type to describe content. The type determines what kind of field will be generated in the Sanity Studio editor, as shown below:

Generating Fields in Sanity Studio Editor

Also, update schemas/siteheader.js:

// schemas/siteheader.js
export default {
  name: 'siteheader',
  title: 'Site Header',
  type: 'document',
  fields: [
      name: 'title',
      title: 'Site header title',
      type: 'string',
      name: 'repoURL',
      title: 'Repo URL',
      type: 'slug',

Next, import and add both the homepage and siteheader schemas to the list of schemas in schema.js:

// schemas/schema.js
import createSchema from 'part:@sanity/base/schema-creator';
import schemaTypes from 'all:part:@sanity/base/schema-type';

// Import both schemas
import homepage from './homepage';
import siteheader from './siteheader';

export default createSchema({
  // We name our schema
  name: 'default',
  types: schemaTypes.concat([
    /* Append to the list of schemas */

The project should update to reflect the schemas, at which point content can be created and published for both the homepage and siteheader schemas.

Create and Publish The Homepage and Siteheader Schemas

Now that we’ve created and published content on Sanity Studio, it’s time to consume the data in Next.js.

Fetch data from Sanity CMS in Next.js

This is where we bridge the gap between headless content and the presentation layer, also known as the head and body, respectively.

By fetching the authored content exposed as data over an API from, we can use it to dynamically populate relevant sections on the landing page. We can follow along with this commit.

By connecting the Next.js app through the @sanity/client package, we can fetch the published data on Let’s install @sanity/client in our Next.js app with the following command:

yarn add @sanity/client

With this package, we can interface with from Next.js. But first, we must feed it an object of configuration properties.

Inside the Next.js project, create a new lib folder with a new file sanity.js:

// /lib/sanity.js
import sanityClient from '@sanity/client';

// See the image above on how to get your projectId and add a new API token
// I added one called "landing page"
const client = sanityClient({
  projectId: 'your-project-id',
  dataset: 'production',
  token: 'api-token', // or leave blank to be anonymous user
  useCdn: false, // `false` if you want to ensure fresh data
  ignoreBrowserTokenWarning: true,

export default client;

These configuration values should be stored and accessed as environmental variables:

Configuration Values Of Environmental Variables

Fetch data in Next.js with GROQ

Now we must fetch data with @sanity/client through getStaticProps with Graph-Relational Object Queries (GROQ), Sanity’s open source query language. We can follow along with this commit.

To proceed, there are two updates we must make in pages/index.js.

First, import the client object returned by sanityClient in lib/sanity.js:

import client from '../lib/sanity';

Next, append the code below to pages/index.js:

// Create a query called siteHeaderQuery
const siteHeaderQuery = `*\[_type == "siteheader"\][0] {
  repoURL {

// Create a query called homepageQuery
const homepageQuery = `*\[_type == "homepage"\][0] {
  "ctaUrl": cta {
  image {

export async function getStaticProps() {
  const homepageData = await client.fetch(homepageQuery);
  const siteHeaderData = await client.fetch(siteHeaderQuery);

  const data = { homepageData, siteHeaderData };

  return {
    props: {
    revalidate: 1,

The returned data from getStaticProps will be available as a prop on the Home component:

export default function Home({ data }) {
  const { siteHeaderData, homepageData } = data;

  // Check your browser console for output
  console.log({siteHeaderData, homepageData});

  // rest of component

If all goes well, we should be ready to dynamically update the landing page content in the Home component.

The landing page image was initially sourced as a static asset from the public folder:

<img className="homepage-img" src="/glass-building-at-the-end-of-a-shadowed-street.jpg" alt="" />

But now it’s been updated to fetch data from

<img className="homepage-img" src={homepageData.image.url} alt={homepageData.subtitle} />

Going forward with and Next.js

The topics and ideas shared in this article are fundamental to structured content management with and how it integrates with Next.js. The full repository was broken into separate commits throughout this post, but you can review the full final demo repository on GitHub. also provides more features and functionalities than what was shown in this post, and the official documentation is a good place to start learning more.

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — .

Adebiyi Adedotun Caught in the web, breaking things and learning fast.

Leave a Reply