Integrate Sanity into your Next App

#ProgrammingThu Aug 15 2024

What could be the best way to manage content for my blog? I mean... what are the options out there? I chose to use "Sanity" this time for my Next.js blog. In this article, we will cover what this service is and how to integrate it into your application.

What is Sanity?

Let’s simply put this way. Sanity is a headless CMS delivers content into your blog. And why did I need this when I already experienced another headless CMS and then moved to local MDN for my blog? I scratched this phrase from Sanity’s landing page.

Sanity gives you total composability. A fully decoupled, real-time content back end. Entirely customizable content workspaces.

The words “composability” and “real-time” caught my eyes while I was surfing through different content managing options like Notion API. To explain theses two words naturally brings us into their two main concept which are “Content Lake” and “Studio”.

Sanity Content Lake

The Sanity Content Lake is where your content is stored and accessed. It runs in the cloud and is fully managed by us. You access the Content Lake via the Sanity API, either by using one of our client libraries or directly via the HTTP API.

This data store use their unique Query Language called “GROQ” (Graph-Relational Object Queries) and it allows us to fetch and manipulate contents flexibly. One more thing I’d like to point out is they are API-First Approach. And I found this functionality makes it easy to integrate and use it when it comes to front-end application. Now you can see why “composability” comes into right fit to describe Sanity Content Lake since we select and assemble contents in various combination.

Sanity Studio

Sanity Studio is a real-time collaborative application for content creation. It's open-source and connects to the hosted Content Lake.

Now we know where we place our contents and how to manipulate with it (Not quite exactly how yet untilthe next subheading). But how can we manage our contents in “real time”? And here “Sanity Studio” comes in hand. Once you install Sanity CLI and run your application, all you need to do is adding “/studio” into at the end of the project’s url. And Voilà now you have a customizable management interface with real time editing function. As we define our content structure (schemas), the ui will generate the appropriate UI for content creation and editing.

Adding the integration

I think that was enough explanation so let’s dig into how we can actually use this. First thing first, configure the Sanity client to connect to your Sanity Content Lake. You can make a project on Sanity and it will gives you a command to Initialize your project with the CLI. And above the section, you can find your PROJECT ID.

Sanity Dashboard

Once you get the project ID and set up as an environment variable, you can add a configuration by adding sanity.cli.js file. And now you can access to the Studio!

// sanity.cli.js
import { defineCliConfig } from "sanity/cli";

export default defineCliConfig({
  api: {
    projectId: "<your-project-id>",
    dataset: "production",
  }
});

Set Schema and Get Posts

It’s time to use GROQ to fetch data from Sanity! If you have initialized a Sanity Studio project, you’ll be able to navigate to the schemas directory. This is typically located at the root of your Sanity Studio project and here is what mine look like:

sanity
 ┣ lib
 ┃ ┣ client.ts
 ┃ ┗ image.ts
 ┣ schemaTypes
 ┃ ┣ post.ts
 ┃ ┗ tag.ts
 ┣ env.ts
 ┗ schema.ts

Write Schemas

Schemas are the main mechanic for describing your content and how to interact with it in the studio.

Let’s define Schema Types for each content type. In my case, I do have post and tag files to define a blog post and tag for each post. Add field items as you wanted to disply into your blog post. It will be something looks like this:

// schemas/post.js
export default {
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string'
    },
    {
      name: 'body',
      title: 'Body',
      type: 'array',
      of: [{type: 'block'}, {type: 'image'}]
    }, 
    ... // slug, excerpt, publishedAt, tags fields
  ]
}; 

Get Posts with GROQ

Once you done define schema types, you need to combines all the individual schemas into the main schema file usually called schema.js or index file of schemas. You can simply do this by import your schema files and include them in the schemaTypes array.

// ./schemas/index.js
import post from './person'

export const schemaTypes = [post]

Then the studio will now let you create a new "post", and provide the form inputs needed to equip your post with a provided field. You can add rules on schema to specify validation rules on your document types and fields as well!

Once you publish your first sanity post, now it’s time to fetch data and see if this actually working! In your Next.js page, you can write a query to retrive the fields data that you typed in on Schema earlier. In my case, this is an individual blog page that has title, slug, excerpt, body as fields.

Sanity Post with Custom Fields

Let's fetch data and see if this actually working! In your project, you can write a query to retrive the fields data that you typed in on Schema earlier. In my case, this is an individual blog page that has title, slug, excerpt, body as fields.

async function getPost(slug: string) {
  const query = `
  *[_type == "post" && slug.current == "${slug}"][0] {
    title,
    slug,
    excerpt,
    body,
  }
  `;

  const post = await client.fetch(query);
  return post;
}

And now you can get post data with a matching slug. You might have a question “What is PortableText tag there?”. Let’s discuss about this in next heading.

const Post = async ({ params }) => {
  const post = await getPost(params?.slug);
  if (!post) notFound();
  
  return (
    <div>
      <h1>{post.title}</h1>
      <PortableText value={post.body} />
    </div>
  );
}

Presenting Portable Text

When you query your Sanity project’s API your rich text content is returned as Portable Text. Portable Text is designed to be used in pretty much any format or markup where you want to render rich text content. (https://www.sanity.io/docs/presenting-block-text)

So basically your content you made in Sanity Studio is a rich text content that needes to be serialized. Like it said, when you fetch the data, it will give you a data as Portable Text. So what is Portable Text? According to Specification for Portable Text, it’s a JSON based rich text specification for modern content editing platforms. They’ve got tooling for generic markup and programming languages and for popular frameworks, and of course they have a helper for React. To render portable text with React, You need to install @portabletext/react library. You can find detailed instruction and guide from here.

npm install --save @portabletext/react

Customizing components

The rendered HTML does not have any styling applied, so you will either render a parent container with a class name you can target in your CSS, or pass custom components if you want to control the direct markup and CSS of each element.

If you making a blog from scratch like me, that probably means that you need a customized component with full control. Like it said, you can pass custom components by adding components prop into PortableText component.

<PortableText
  value={post?.body}
  components={myPortableTextComponents}
/>

How does myPortableTextComponents look like? These are the overridable/implementable keys like types, marks, block, list and so on. You can all the available components from here. Let’s say I want to style custom image object with full width and auto height. You can add inline style here, and passed value on through parameter.

const myPortableTextComponents = {
  types: {
    image: ({ value }) => (
      <Image
        src={urlForImage(value).url()}
        alt={value.alt || ""}
        width={0}
        height={0}
        sizes="100vw"
        style={{ width: "100%", height: "auto" }}
      />
    ),
  }
}

If custom component is getting bigger, you can alwasy seperate into a file and then import it! I had gigantic lines to have custom style for code object, and decided it to split it. And now it looks much more simple.

const myPortableTextComponents = {
  types: {
    code: myCustomCodeComponent,
  }
}

Final Thoughts

I think this is pretty much about what have I explored while I was use Sanity for the first time in my life. Now I’m trying to setup Visual Editing. As I just began to use this feature, I’d love to write a post how I like this compared to other content manager after giving me some time to play around. So far, I love how easy to access to the contents and manipulate it! I am hoping that this boost my speed up to write more contents and deliver it to you guys. See you in the next post!