Back

Dec 2, 2022

Dec 2, 2022

How to Create Sanity Headless CMS in Next.js

Learn how you can create text, images, and other media types with Sanity Headless CMS and display that content in your Next.js application.

decorative cover image showing two doors
decorative cover image showing two doors
decorative cover image showing two doors

What is Sanity.io?

Sanity.io is a unified content platform where you can store and manage content for a website or application. Unlike traditional Content Management Systems (CMS) like WordPress or Wix, which keep your content and display it on the front-end for you, Sanity headless CMS stores your content but lets you decide how you want that content to be displayed. With Sanity.io, you can show your content on different web and mobile applications.

Next.js, a framework that seamlessly integrates with Sanity.io, proves to be an excellent choice for leveraging the platform's capabilities. Next.js is a popular React framework used by developers to render React applications server-side to improve Search Engine Optimization (SEO) performance. Foundation is a great framework for building responsive sites as well.

In this tutorial, We will go through how you can create text, images, and other media types with Sanity.io and display that content in your Next.js application.

Prerequisites

This tutorial assumes that you have a basic understanding of how Sanity Content Management Systems (CMS) and Sanity Next.js work. Learn more about why you should use Next.js in your React projects to further your understanding of the topic.

Why should you use Sanity?

Sanity CMS lets you treat content like data by creating a content lake. In a content lake, all your content is structured together and readily available for collaborative authoring. It is a real-time database for content in the Sanity studio. The Sanity studio is an open-source single-page application that uses schemas to let users organize content within the system.

What is a Schema?

Schemas are the structures in a database for organizing images, texts, tables, attributes, references, and other types of media. Schemas are a plus for building server-side rendering applications with Next.js or Gatsby by providing additional information about the content of pages to web crawlers.

Types of Schemas in Sanity

These are the most common schema types you will encounter when working with Sanity.io:

  • Object: Defines reusable and nested fields for complex data relationships.

  • String: Represents plain text content.

  • Number: Stores numerical values.

  • Boolean: Represents true or false values.

  • Array: Creates lists or arrays of other schema types.

  • Reference: Establishes relationships between documents.

  • Image: Specifically designed for storing images.

  • Date: Stores date and time values.

Understanding these schema types empowers you to create structured and dynamic content in Sanity.io.

Getting started with Sanity Headless CMS

First things first, we need to have Node.js installed. If you don’t have Node.js, download it here. Once we have that downloaded and installed, let’s install the Sanity CLI using the command below:

npm install -g @sanity/cli

Save this snippet

Once we have installed the Sanity CLI, we will initialize a new Sanity headless CMS project by running the command:

sanity init

Save this snippet

After running the command, we will create an account with Sanity using any of the signup methods below:

sanity.io project set up

Once the authentication finishes, you’ll have to give a name to our project. I named it “sanity_tutorial”, but you can call yours whatever you want.

screenshot of successful login confirmation

Click on “Enter,” and you'll specify if you want your project to be public or private. Choose "public" by inputting "Y" and clicking “Enter.”

screenshot showing project privacy settings

Declare an existing path to set up the project on your PC.

Click “Enter” and select a schema or template. You’ll be choosing a clean template with no predefined schemas. After clicking “Enter,” you’ll have a project with files and node dependencies in your folder.

screenshot showing the files and node dependencies

Now that you've created your project, you can start using some helpful scripts provided by Sanity. By default, there are two scripts in the project's “package.json” file:

  1. sanity start - This will run our studio locally at localhost:3333.

  2. sanity build - This will generate our current Sanity configuration to a static build.

Getting into the Sanity Studio

The studio UI is pretty cool. We’ll have a look at how it looks locally on our browser. But first, we’ll have to run the command:

sanity start

Save this snippet

Next, we’ll visit localhost:3333. After that, sanity will connect, and we’ll sign in with our earlier method when setting up our project.

screenshot showing sanity sign in

Once we have signed into the studio, we’ll notice that it has no content or schemas. The studio is empty because we had selected “Clean project with no predefined schemas” before creating the studio. So now, let’s add some schemas.

Creating Schemas

Back in our code editor, we will find a file called “schema.js” under the schema folder of our project. This file is where all the schemas we create will be imported and used. Let’s read the comments provided in the file to have a bit more understanding of what’s going on before we proceed:

// First, we must import the schema creator
import createSchema from 'part:@sanity/base/schema-creator'
// Then import schema types from any plugins that might expose them
import schemaTypes from 'all:part:@sanity/base/schema-type'
// Then we give our schema to the builder and provide the result to Sanity
export default createSchema({
    // We name our schema
    name: 'default',
    // Then proceed to concatenate our document type
    // to the ones provided by any plugins that are installed
    types: schemaTypes.concat([
    /* Your types here! */
  ]),
})

Save this snippet

Create a file in the schemas folder and call it firstSchema.js. In the file, we have to declare a type for the schema. Depending on the schema type we use, specific properties are required to go along it. We will use the document type for this current schema, which requires a name, title, and input field. The key for input fields in Sanity, called “fields”, is a property that lets us declare the kind of input to expect. We’ll be using the string type input field.

export default {
  type: "document", //value must be a schema type e.g document
  name: "author", //value can be any word
  title: "Author", //value can be any word
  fields: [
    {
      type: "string",
      name: "name",
      title: "Author's Name"
    }
  ]
}

Save this snippet

Let’s go back to schema.js and delete the comments to have a clearer view of what we’re about to do. Then, just above the createSchema component, import the schema we’ve just created, firstSchema, as a type into the createSchema component like so:

import firstSchema from "./firstSchema"
export default createSchema({
    name: 'default',
    types: schemaTypes.concat([
    firstSchema,
  ]),
})

Save this snippet

Save the changes and visit localhost:3333. Next, we’ll see the title of the schema we created, Author, displayed. Author contains all the schema properties we defined in our project. With these properties, we’ll be able to input and publish new content from the studio to the application.

screenshot showing localhost:3333, the schema title and author

Click on Publish, and the Author will be saved to the studio.

Referencing Schemas

Reference is a schema type for modeling relations between documents. It models one or more relations and stores the reference in an array. Let’s create a second file under the schemas folder called secondSchema.js. This schema will refer to the first schema we created, but its properties will be quite different. The schema will be of the type document with the name “book” and title “Book” but with input fields of the types “string,” “image,” and “reference.” The “reference” type requires an additional property called “to,” which will contain the single or multiple schemas we want to refer to in “book.” In this case, we want the “book” to refer to its author from the first schema.

export default {
  type: "document",
  name: "book",
  title: "Book",
  fields: [
    {
      type: "string",
      name: "name",
      title: "Book Name"
    },
    {
      type: "image",
      name: "image",
      title: "Book Image"
    },
    {
      type: "reference",
      name: "author",
      title: "Author",
      to: { type: "author"}
    }
  ]
}

Save this snippet

We will also need to provide a slug as an ID to navigate between different pages in our Next.js application later. Let’s add that right under the string type like so:

fields: [
{
type: "string",
name: "name",
title: "Book Name"
},
{
type: "slug",
name: "slug",
title: "Slug"
},
{
type: "image",
name: "image",
title: "Book Image"
},
{
type: "reference",
name: "author",
title: "Author",
to: { type: "author"}
}
]

Save this snippet

To see this schema and its reference in the studio, import secondSchema into schema.js and insert it in the createSchema component.

import firstSchema from "./firstSchema"
import secondSchema from "./secondSchema";
export default createSchema({
name: 'default',
types: schemaTypes.concat([
firstSchema,
secondSchema
]),
})

Save this snippet

Save the changes and visit localhost:3333. Then, click “Book,” and we’ll see the second schema’s properties. Let’s give the book a name. For the slug, use an ID of 1 because this is our first book. Add an image for the book, and for the last input field, which has the reference type, select an author from the dropdown.

screenshot showing locahost:3333 content, book, and publish

Great! Let’s see how we can use this content across multiple front-end applications like Next.js.

Connecting Sanity.io to Next.js

Let’s set up our Next.js Sanity project by running the command:

npx create-next-app

Save this snippet

In the index.js file of the pages folder, delete all the content in the “Home()” functional component and replace it with what we want the header of our homepage to be. First, we’ll create a div tag containing just the header:

export default function Home() {
return (
Books
)
}

Save this snippet

Let’s have the name of our book right below the h1 tag. But how do we get the book we created in the Sanity studio into this component? We’ll use a special server-side rendering function from Next.js called getServerSideProps and a query language from Sanity.io called “GROQ.” Right below the Home() component, we will export an asynchronous getServerSideProps() function. Inside this function will be a GROQ query describing the type or nature of the information we want to fetch, a URL, a response, and a return statement. Since we want to fetch the book’s content from the studio, this is what the query will look like:

const query = `*[ _type == "book" ]`

Save this snippet

And if we were to fetch the author’s content, the query would look like this:

const query = `*[ _type == "author" ]`

Save this snippet

Now that we have our query, we will have to encode it with encodeURIComponent().

const query = encodeURIComponent(`*[ _type == "book" ]`)

Save this snippet

For the next step, let’s get the project ID from https://www.sanity.io. First, sign in using the previous method. Once signed in, click on the Sanity project we are working with, and you will find your project ID. Copy the project ID and paste it into an environment variable. Next, we will have to set a URL for the fetch request we are about to make with the query. The URL will contain our project ID and our query.

const url = `https://YOUR_PROJECT_ID.api.sanity.io/v1/data/query/production?query=${query}`

Save this snippet

Now, we need to fetch a response from the URL and convert that to JSON. The response is going to return three objects named “ms,” “query,” and “result.” We are only interested in the result since that is where all of our content is stored, so we will get that result and return it as props in getServerSideProps().

const response = await fetch(url).then(res => res.json())
return {
props: {
book: response.result
}

Save this snippet

In the end, we will get a function in Next.js that fetches content from Sanity:

export async function getServerSideProps(){
  const query = encodeURIComponent(`*[ _type == "book" ]`)
  const url = `https://YOUR_PROJECT_ID.api.sanity.io/v1/data/query/production?query=${query}`
  const response = await fetch(url).then(res => res.json())
  return {
    props: {
      book: response.result
    }
  }
}

Save this snippet

Now that we have our content in our application, we have to display it. So in the Home() functional component, let’s parse in the props from getServerSideProps() and use the props underneath our header. The result from Sanity is an array, and we do have just one book created in the studio, but we will be using a mapping just in case we want to add more content to the studio in the future.

export default function Home({ book }) {
  return (
    Books          {book.map((b) => {         return (                      {b.name}                 )      })}      
 )
}

Save this snippet

Let’s save this and run the app in development mode by running the command:

npm run dev

Save this snippet

And visiting localhost:3000:

sanity tutorial book being displayed on a localhost:3000 webpage

We’ll see our book displayed! Now let’s get the rest of our content on a different page. This way, if we create more content in the studio, we will be able to see a list of multiple books on our homepage and their details on different pages.

Displaying More Sanity Headless CMS Content

Next.js is amazing when it comes to moving between pages. The process is called “Dynamic Routing”. Remember, we used a slug for our second schema (Book) at the Sanity studio to serve as a custom unique identifier. We will input that slug at the end of our URL to navigate between the pages in Next.js. Let’s create a subfolder in pages called “bookInfo”. The “bookInfo” folder will have a file for our slugs called “[slug].js”. The brackets tell Next.js to treat the file as a dynamic route. Inside “[slug].js”, export an asynchronous getServerSideProps() which will expect a context as an argument.

export const getServerSideProps = async context => {}

Save this snippet

In getServerSideProps(), we will query the slug from the context and store it in a variable called pageSlug like so:

const pageSlug = context.query.slug

Save this snippet

Let’s create our query with GROQ, but this time we will use the page’s slug and ensure the slug corresponds with the properties of “book.”

const query = encodeURIComponent(`*[ slug.current == "${pageSlug}" && _type == "book" ]`)

Save this snippet

Now, we’ll create a URL like we did before and fetch a response.

const response = await fetch(url).then(res => res.json())

Save this snippet

The response has a result object which contains our book’s name, the book’s image, and the book’s author. We expect the book to have only one detail, so we do not need to map through the result. Let’s store the book’s name into a variable called “book” like so:

const book = response.result[0].name

Save this snippet

To get our image, we must download a package from Sanity, which will provide a source URL. Run the command:

npm install @sanity/image-url

Save this snippet

And import the imageUrlBuilder() from the package like so:

import imageUrlBuilder from "@sanity/image-url"

Save this snippet

Inside getServerSideProps(), We have to provide our project ID and dataset as an object to imageUrlBuilder() and store it into a variable like so:

const builder = imageUrlBuilder({
  projectId: "YOUR_PROJECT_ID",
  dataset: "production"
})

Save this snippet

With that variable, let’s call the image builder and parse the original return value for our book’s image.

const bookImage = builder.image(response.result[0].image).url()

Save this snippet

Lastly, we’ll need to get our book’s author. The author in the second schema is not of type string. Instead, the author is of the type reference, so we will make a GROQ query using the reference content in the second schema’s author to get the string content of the first schema’s author. The content of the reference type is also the same as the “_id” type of our first schema. _id is an extended text format generated by Sanity for organizing content. Let’s make a query for our author using an asynchronous function outside of getServerSideProps(). We’ll call this function “getAuthor”, and it will contain the following:

async function getAuthor(id){
  const query = encodeURIComponent(`*[ _id == "${id}" ]`)
  const url = `https://YOUR_PROJECT_ID.api.sanity.io/v1/data/query/production?query=${query}`
  const response = await fetch(url).then(res => res.json())
    return(response.result[0].name)
}

Save this snippet

So we are saying we want to make a query for the “_id“ type in our studio and return the name of the author. Back in getServerSideProps(), let’s use the function we just created to get our author. The author contains two objects: the “_type” and “_ref”. We will parse in “_ref” as the id for the function like so:

const author = await getAuthor(response.result[0].author._ref)

Save this snippet

Sweet! We’ve got all of our content. Once we return them as props, we’ll get something like this:

export const getServerSideProps = async details => {
  const pageSlug = details.query.slug
  const query = encodeURIComponent(`*[ slug.current == "${pageSlug}" && _type == "book" ]`)
  const url = `https://YOUR_PROJECT_ID.api.sanity.io/v1/data/query/production?qery=${query}`
  const response = await fetch(url).then(res => res.json())
  const book = response.result[0].name
  const builder = imageUrlBuilder({
    projectId: "YOUR_PROJECT_ID",
    dataset: "production"
})
const bookImage = builder.image(response.result[0].image).url()
const author = await getAuthor(response.result[0].author._ref)
return {
  props: {
    book: book,
    bookImage: bookImage,
    author: author
  }
 }
}

Save this snippet

All we have to do now is parse the three props into our default functional component like so:

export default function BookInfo({ book, bookImage, author })
  {return(
              {book} is a book published by {author}     
  )
}

Save this snippet

Let’s return to our “index.js” file and connect it to the “[slug].js” with useRouter(), a function in Next.js that lets us move smoothly between clicked pages in the application. For example, when we click on a book on our homepage, Next.js will change our route to a page containing the book’s details. So, in index.js, import the useRouter():

import { useRouter } from "next/router"

Save this snippet

And store it in a const variable called router. Make sure the variable is inside the default functional component:

const router = useRouter()

Save this snippet

We’ll use the router and its push method in an onClick() event for the li tag, which contains the name of our books. Inside the push method, we’ll provide the slug of the page we want to navigate to like so:

{book.map((b) => {
  return (
 router.push(`/bookInfo/${b.slug.current}`)} key={b}>
      {b.name}
  )
})}

Save this snippet

Save, go to localhost:3000, and click on the book “Sanity Tutorial”:

screenshot showing locahost:3333 with a header image that shows suspended tv screens in a large room, and the book title and author name

And there’s all of our content :). Sanity.io lets us take the wheel when styling content for our front-end. We can add more books and authors to the studio, and they'll be rendered to the front-end automatically.

Conclusion

Sanity.io is a flexible management system that lets users structure content however they see fit. Sanity.io CMS is great for working with static and server-side rendered sites. Users can create blogs, portfolios, eCommerce websites, and many more applications with the Sanity headless CMS. It takes a little time to get started in the studio, but even less time when adding new content to an existing schema. In this tutorial, we covered a few of the features Sanity has to offer, but there are many more features you can explore on our blog to create fast and responsive websites, such as this one on creating a headless WordPress site.

Written by

Written by

Sage Osoro

Sage Osoro

SHARE

SHARE

Title

Title

our newsletter

Sign up for The Pieces Post

Check out our monthly newsletter for curated tips & tricks, product updates, industry insights and more.

our newsletter

Sign up for The Pieces Post

Check out our monthly newsletter for curated tips & tricks, product updates, industry insights and more.

our newsletter

Sign up for The Pieces Post

Check out our monthly newsletter for curated tips & tricks, product updates, industry insights and more.