How to Create Sanity Headless CMS in Next.js
Published: 2022-12-02 17:32:37

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/sanity_io_9bec7f548450ecf24183e8312eb1ed4e.jpg" alt="How to use Sanity Headless CMS in Next.js."/></figure>

## What is Sanity.io?

[Sanity.io](https://www.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](https://nextjs.org/), 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](https://code.pieces.app/blog/foundation-the-best-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](https://medium.com/@sageosoro/why-you-should-use-next-js-in-all-your-react-projects-a8e634bba4b4) 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](https://nodejs.org/en/download/). 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](https://mariah.pieces.cloud/?p=230848aeac)

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](https://mariah.pieces.cloud/?p=59c2439a2e)

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

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image3_99289fe2d99c194dfd574264e7eba3df.png" alt="The Sanity.io signup commands. "/></figure>

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.

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image9_2fe85ef6ae715a51f7fa0d875d7ceadc.png" alt="A successful authentication message."/></figure>

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.”

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image7_b803144f5e1329d561c334d97568b9b0.png" alt="Choosing a public project. "/></figure>

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

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image10_8d93cdd19025a7eb40d508e1937ad4d3.png" alt="Declaring a file path."/></figure>

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.

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image8_25901007038dbbcfe56327edb7f9dd21.png" alt="Selecting a Sanity schema for our project."/></figure>

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.
1. **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](https://mariah.pieces.cloud/?p=3a2946bcc8)

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.

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image6_0c3e80e94e29eba16a70fbd12481a5b1.png" alt="The Sanity io sign in screen."/></figure>

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](https://mariah.pieces.cloud/?p=c0e74388a8)

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](https://mariah.pieces.cloud/?p=d58644b802)

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](https://mariah.pieces.cloud/?p=236b4fbb28)

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.

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image1_956f1c4c768e7583daa86bbf13d0d635.png" alt="Declaring the Author in our project. "/></figure>

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](https://mariah.pieces.cloud/?p=9e174ebace)

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](https://mariah.pieces.cloud/?p=bc1f4fa5a9)

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](https://mariah.pieces.cloud/?p=06d84c90b9)

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.

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image2_bddb7316cddc33ff7bce6ec5d269d845.png" alt="Assigning necessary values to our Book schema."/></figure>

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](https://mariah.pieces.cloud/?p=32cb43ab64)

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 (

<div>
<h1>Books</h1>
</div>
)
}
```

[Save this snippet](https://mariah.pieces.cloud/?p=8d4f49b27b)

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](https://mariah.pieces.cloud/?p=e39845a191)

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

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

[Save this snippet](https://mariah.pieces.cloud/?p=78134aac64)

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

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

[Save this snippet](https://mariah.pieces.cloud/?p=887344824c)

For the next step, let’s get the project ID from[ https://www.sanity.io](https://www.sanity.io/login). 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](https://mariah.pieces.cloud/?p=42de4a99e3)

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](https://mariah.pieces.cloud/?p=c50f4f8915)

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](https://mariah.pieces.cloud/?p=b03a4e9855)

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 (
   <div>
    <h1>Books</h1>
    <ul>
      {book.map((b) => {
         return (
          <li key={b}>
            {b.name}
          </li>
       )
      })}
    </ul>
  </div>
 )
}
```

[Save this snippet](https://mariah.pieces.cloud/?p=7d934bb4ee)

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

```
npm run dev
```

[Save this snippet](https://mariah.pieces.cloud/?p=f862419f14)

And visiting localhost:3000:

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image11_fea8c23b2da8c5ad8efc8e358a55877d.png" alt="The displayed book in the browser."/></figure>

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](https://mariah.pieces.cloud/?p=7f874c9f53)

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](https://mariah.pieces.cloud/?p=992243bbc5)

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](https://mariah.pieces.cloud/?p=a5cb48b4ee)

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](https://mariah.pieces.cloud/?p=9d6540835c)

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](https://mariah.pieces.cloud/?p=8e764fa22b)

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](https://mariah.pieces.cloud/?p=b59b459975)

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

```
import imageUrlBuilder from "@sanity/image-url"
```

[Save this snippet](https://mariah.pieces.cloud/?p=3eae47af6e)

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](https://mariah.pieces.cloud/?p=261d47899d)

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](https://mariah.pieces.cloud/?p=db71438fd0)

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](https://mariah.pieces.cloud/?p=2d1047823c)

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](https://mariah.pieces.cloud/?p=47c54c91d0)

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](https://mariah.pieces.cloud/?p=2bd2469d64)

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(
    <div>
      <img src={bookImage} height="350" />
        <div>{book} is a book published by <b>{author}</b> </div>
    </div>
  )
}
```

[Save this snippet](https://mariah.pieces.cloud/?p=15124280e5)

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](https://mariah.pieces.cloud/?p=ae644f8a0b)

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](https://mariah.pieces.cloud/?p=abab43ac3a)

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 (
    <li onClick={() => router.push(`/bookInfo/${b.slug.current}`)} key={b}>
      {b.name}
    </li>
  )
})}
```

[Save this snippet](https://mariah.pieces.cloud/?p=60ee4c8ad6)

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

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image4_0e1f2a3033181d5553eea4e0e78611d9.png" alt="The Sanity Tutorial book on the web."/></figure>

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](https://code.pieces.app/blog/building-a-headless-wordpress-site-with-vuejs).