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
Once we have installed the Sanity CLI, we will initialize a new Sanity headless CMS project by running the command:
sanity init
After running the command, we will create an account with Sanity using any of the signup methods below:
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.
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.”
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.
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:
- sanity start - This will run our studio locally at localhost:3333.
- 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
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.
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! */
]),
})
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"
}
]
}
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 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.
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"}
}
]
}
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"}
}
]
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 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.
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
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>
)
}
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" ]`
And if we were to fetch the author’s content, the query would look like this:
const query = `*[ _type == "author" ]`
Now that we have our query, we will have to encode it with encodeURIComponent()
.
const query = encodeURIComponent(`*[ _type == "book" ]`)
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}`
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
}
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
}
}
}
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>
)
}
Let’s save this and run the app in development mode by running the command:
npm run dev
And visiting localhost:3000:
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 => {
}
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
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" ]`)
Now, we’ll create a URL like we did before and fetch a response.
const response = await fetch(url).then(res => res.json())
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
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
And import the imageUrlBuilder()
from the package like so:
import imageUrlBuilder from "@sanity/image-url"
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"
})
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()
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)
}
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)
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
}
}
}
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>
)
}
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"
And store it in a const variable called router
. Make sure the variable is inside the default functional component:
const router = useRouter()
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, go to localhost:3000, and click on the book “Sanity Tutorial”:
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.