0% found this document useful (0 votes)
36 views24 pages

Integrating Sanity CMS with Next.js

This document provides a comprehensive guide on integrating Sanity CMS with a Next.js project, detailing the advantages of using a headless CMS. It covers the setup process, including creating schemas, configuring the environment, and utilizing the Sanity Studio for content management. Additionally, it outlines the necessary technologies, project structure, and code snippets for effective implementation.

Uploaded by

prashant77454
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
36 views24 pages

Integrating Sanity CMS with Next.js

This document provides a comprehensive guide on integrating Sanity CMS with a Next.js project, detailing the advantages of using a headless CMS. It covers the setup process, including creating schemas, configuring the environment, and utilizing the Sanity Studio for content management. Additionally, it outlines the necessary technologies, project structure, and code snippets for effective implementation.

Uploaded by

prashant77454
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Sanity CMS with Nextjs

Introduction
This guide covers the integration of Sanity CMS into a [Link] project. Sanity CMS
is a headless content management system that provides a flexible and powerful
way to manage content. By integrating Sanity CMS, you can build an advanced
studio (dashboard) within your [Link] application, facilitating easier content
management.

What is a Headless CMS?


A headless CMS is a backend-only content management system that decouples
the content repository from the presentation layer. Unlike traditional CMSs, which
tightly couple content management with the frontend, a headless CMS allows
developers to deliver content across various devices and platforms using APIs.

Advantages of a Headless CMS


Flexibility: Content can be delivered to any frontend framework or device,
providing a versatile solution for modern web development.

Scalability: Headless CMSs are designed to handle high traffic and large
volumes of content, making them suitable for growing businesses.

Improved Performance: By separating the content management and


presentation layers, you can optimize each independently, leading to faster
load times and better user experiences.

How It Works

Sanity CMS with Nextjs 1


1. Schemas: In Sanity CMS, schemas define the structure of your content. You
create schemas to specify what fields your content types (e.g., blogs, authors)
will have.

2. API: Sanity provides a powerful API to query and manage your content. You
can use GROQ (Graph-Relational Object Queries) to fetch and manipulate
data.

3. Studio: The Sanity Studio is a customizable, React-based interface where


content editors can manage and organize content. It is integrated into the
[Link] application to provide a seamless content management experience.

By following this guide, you will learn how to set up a [Link] project, configure
Sanity CMS, and create schemas and queries to manage your content effectively.

Technologies Used
Nextjs 14 (App Router)

Tailwind CSS

TypeScript

Sanity

Project Setup
Create a new directory for the project.

Sign up to the Sanity website and get the SANITY_API_READ_TOKEN,


NEXT_PUBLIC_SANITY_DATASET, and NEXT_PUBLIC_SANITY_PROJECT_ID.

Paste the below command to initialize a nextjs project. (you may also use
npm/yarn/bun)

pnpx create-next-app@latest
pnpm create sanity@latest

Exchange the [Link] file to install all dependencies.

Sanity CMS with Nextjs 2


{
"name": "sanity-blog",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,cs
},
"dependencies": {
"@portabletext/react": "^3.1.0",
"@sanity/client": "^6.21.1",
"@sanity/image-url": "^1.0.2",
"@sanity/vision": "^3.53.0",
"@types/node": "22.1.0",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"autoprefixer": "10.4.20",
"bcryptjs": "^2.4.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"eslint": "9.8.0",
"eslint-config-next": "14.2.5",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.426.0",
"mongoose": "^8.5.2",
"next": "14.2.5",
"next-sanity": "^9.4.4",
"next-themes": "^0.3.0",
"postcss": "8.4.41",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-hook-form": "^7.52.2",

Sanity CMS with Nextjs 3


"sanity": "^3.53.0",
"styled-components": "^6.1.12",
"tailwind-merge": "^2.4.0",
"tailwindcss": "3.4.9",
"typescript": "5.5.4",
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
"@tailwindcss/typography": "^0.5.14",
"@tanstack/eslint-plugin-query": "^5.51.15",
"@types/bcryptjs": "^2.4.6",
"@types/jsonwebtoken": "^9.0.6",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.5"
}
}

App Structure

├── app
│ ├── [Link]
│ ├── [Link]
│ ├── blogs
│ │ ├── [Link]
│ │ ├── [Link]
│ │ ├── [slug]
│ │ │ └── [Link]
│ │ └── add
│ │ └── [[...index]]/[Link]
├── components
│ ├── ui
│ │ ├── [Link]
│ │ └── ...

Sanity CMS with Nextjs 4


│ ├── [Link]
│ └── ...
├── sanity
│ ├── lib
│ │ ├── [Link]
│ │ ├── [Link]
│ │ ├── [Link]
│ │ └── [Link]
│ ├── schemas
│ │ ├── [Link]
│ │ ├── [Link]
│ │ ├── [Link]
│ │ └── [Link]
│ └── schemas
├── styles
│ └── [Link]
├── [Link]
├── [Link]
├── [Link]
└── [Link]

Environment Variables (.env)

SANITY_API_READ_TOKEN=
NEXT_PUBLIC_SANITY_DATASET=
NEXT_PUBLIC_SANITY_PROJECT_ID=

Sanity configuration
Sign up to [Link].

Sanity CMS with Nextjs 5


Create a new project.

Generate Token for your project.

Sanity CMS with Nextjs 6


Add [Link]

/**
* This configuration is used to for the Sanity Studio that’s
*/

import { visionTool } from "@sanity/vision"


import { defineConfig } from "sanity"
import { deskTool } from "sanity/desk"

// Go to [Link] to learn h
import { apiVersion, dataset, projectId } from "./sanity/env"
import { schema } from "./sanity/schema"

export default defineConfig({


basePath: "/blogs/add",
projectId,
dataset,
// Add and edit the content schema in the './sanity/schema
schema,
plugins: [
deskTool(),
// Vision is a tool that lets you query your content with
// [Link]

Sanity CMS with Nextjs 7


visionTool({ defaultApiVersion: apiVersion }),
],
})

Add [Link]

/**
* This configuration file lets you run `$ sanity [command]`
* Go to [Link] to learn more.
**/
import { defineCliConfig } from "sanity/cli"

const projectId = [Link].NEXT_PUBLIC_SANITY_PROJECT_ID


const dataset = [Link].NEXT_PUBLIC_SANITY_DATASET

export default defineCliConfig({ api: { projectId, dataset }

Sanity Integration
We will add schemas and queries to get our data to add sanity.

/lib folder

/[Link]

import { createClient } from "next-sanity"

import { apiVersion, dataset, projectId, useCdn } from ".

export const client = createClient({


apiVersion,
dataset,
projectId,
useCdn,
})

Sanity CMS with Nextjs 8


/[Link]

import { client } from "@/sanity/lib/client"


import createImageUrlBuilder from "@sanity/image-url"
import imageUrlBuilder from "@sanity/image-url"
import type { Image } from "sanity"

import { dataset, projectId } from "../env"

const builder = imageUrlBuilder(client)

const imageBuilder = createImageUrlBuilder({


projectId: projectId || "",
dataset: dataset || "",
})

export const urlForImage = (source: Image) => {


return imageBuilder?.image(source).auto("format").fit("m
}

export function getImgUrl(obj: object, width: number = 300


if (!obj) return "/[Link]"
return [Link](obj).width(width).height(height).ur
}

/[Link]

// ./nextjs-app/sanity/lib/[Link]

import { groq } from "next-sanity"

// Get all blogs


export const blogsQuery = groq`*[_type == "blog" && define
_id, title, slug, mainImage, publishedAt, description
}`

Sanity CMS with Nextjs 9


// Get a single blog by its slug
export const blogQuery = groq`*[_type == "blog" && [Link]
title, mainImage, body, author -> {name, image}, publi
}`

// Get a single blog by its slug


export const blogMetaDataQuery = groq`*[_type == "blog" &&
title, mainImage
}`

// Get all blog slugs


export const blogPathsQuery = groq`*[_type == "blog" && de
"params": { "slug": [Link] }
}`

[Link]

import "server-only"

import { draftMode } from "next/headers"


import { client } from "@/sanity/lib/client"
import type { QueryParams } from "@sanity/client"

const DEFAULT_PARAMS = {} as QueryParams


const DEFAULT_TAGS = [] as string[]

export const token = [Link].SANITY_API_READ_TOKEN

export async function sanityFetch<QueryResponse>({


query,
params = DEFAULT_PARAMS,
tags = DEFAULT_TAGS,
}: {
query: string

Sanity CMS with Nextjs 10


params?: QueryParams
tags?: string[]
}): Promise<QueryResponse> {
const isDraftMode = draftMode().isEnabled
if (isDraftMode && !token) {
throw new Error("The `SANITY_API_READ_TOKEN` environme
}
// const isDevelopment = [Link].NODE_ENV === "devel

return [Link]({ useCdn: false }).fetch<QueryR


// cache: isDevelopment || isDraftMode ? undefined : "
...(isDraftMode && {
token: token,
perspective: "previewDrafts",
}),
next: {
...(isDraftMode && { revalidate: 30 }),
tags,
},
})
}

/schemas folder

/[Link]

import { defineField, defineType } from "sanity"

export default defineType({


name: "author",
title: "Author",
type: "document",
fields: [
defineField({
name: "name",

Sanity CMS with Nextjs 11


title: "Name",
type: "string",
}),
defineField({
name: "slug",
title: "Slug",
type: "slug",
options: {
source: "name",
maxLength: 96,
},
}),
defineField({
name: "image",
title: "Image",
type: "image",
options: {
hotspot: true,
},
fields: [
{
name: "alt",
type: "string",
title: "Alternative Text",
},
],
}),
defineField({
name: "bio",
title: "Bio",
type: "array",
of: [
{
title: "Block",
type: "block",
styles: [{ title: "Normal", value: "normal" }],

Sanity CMS with Nextjs 12


lists: [],
},
],
}),
],
preview: {
select: {
title: "name",
media: "image",
},
},
})

/[Link]

import { defineArrayMember, defineType } from "sanity"

export default defineType({


title: "Block Content",
name: "blockContent",
type: "array",
of: [
defineArrayMember({
title: "Block",
type: "block",
styles: [
{ title: "Normal", value: "normal" },
{ title: "H1", value: "h1" },
{ title: "H2", value: "h2" },
{ title: "H3", value: "h3" },
{ title: "H4", value: "h4" },
{ title: "Quote", value: "blockquote" },
],
lists: [{ title: "Bullet", value: "bullet" }],
marks: {

Sanity CMS with Nextjs 13


decorators: [
{ title: "Strong", value: "strong" },
{ title: "Emphasis", value: "em" },
],
annotations: [
{
title: "URL",
name: "link",
type: "object",
fields: [
{
title: "URL",
name: "href",
type: "url",
},
],
},
],
},
}),
defineArrayMember({
type: "image",
options: { hotspot: true },
fields: [
{
name: "alt",
type: "string",
title: "Alternative Text",
},
],
}),
],
})

/[Link]

Sanity CMS with Nextjs 14


import { defineField, defineType } from "sanity"

export default defineType({


name: "blog",
title: "Blog",
type: "document",
fields: [
defineField({
name: "title",
title: "Title",
type: "string",
}),
defineField({
name: "slug",
title: "Slug",
type: "slug",
options: {
source: "title",
maxLength: 96,
},
}),
defineField({
name: "author",
title: "Author",
type: "reference",
to: { type: "author" },
}),
defineField({
name: "mainImage",
title: "Main image",
type: "image",
options: {
hotspot: true,
},
fields: [

Sanity CMS with Nextjs 15


{
name: "alt",
type: "string",
title: "Alternative Text",
},
],
}),
defineField({
name: "tags",
title: "Tags",
type: "array",
of: [{ type: "reference", to: { type: "tags" } }],
}),
defineField({
name: "publishedAt",
title: "Published at",
type: "datetime",
}),
defineField({
name: "body",
title: "Body",
type: "blockContent",
}),
],

preview: {
select: {
title: "title",
author: "[Link]",
media: "mainImage",
},
prepare(selection) {
const { author } = selection
return { ...selection, subtitle: author && `by ${aut
},

Sanity CMS with Nextjs 16


},
})

/[Link]

import { defineField, defineType } from "sanity"

export default defineType({


name: "tags",
title: "Tags",
type: "document",
fields: [
defineField({
name: "tag",
title: "Tag",
type: "string",
}),
defineField({
name: "slug",
title: "Slug",
type: "slug",
options: {
source: "tag",
maxLength: 24,
},
}),
defineField({
name: "active",
title: "Active",
type: "boolean",
}),
],
})

[Link]

Sanity CMS with Nextjs 17


export const apiVersion = [Link].NEXT_PUBLIC_SANITY_API_
export const dataset = assertValue([Link].NEXT_PUBLIC_SA
export const useCdn = false

export const projectId = assertValue(


[Link].NEXT_PUBLIC_SANITY_PROJECT_ID,
"Missing environment variable: NEXT_PUBLIC_SANITY_PROJECT_I
)

function assertValue<T>(v: T | undefined, errorMessage: strin


if (v === undefined) {
throw new Error(errorMessage)
}

return v
}

[Link]

import { type SchemaTypeDefinition } from "sanity"


import author from "./schemas/author"
import blockContent from "./schemas/block-content"
import blog from "./schemas/blog"
import tags from "./schemas/tags"

export const schema: { types: SchemaTypeDefinition[] } = {


types: [blog, author, tags, blockContent],
}

Frontend(UI) Setup
Here, It has mainly 4 pages (routes).

Sanity CMS with Nextjs 18


├── /
│ ├── /blogs
│ │ ├── /add
│ │ └── /[slug]

Home route

homepage

import Navbar from "@/components/navbar"

export default function HomeLayout({ children }: { childre


return (
<>
<Navbar />
<p> Make a landing page for your blog. </p>
</>
)
}

Blog route

[Link]

import type { Metadata } from "next"


import { getImgUrl } from "@/sanity/lib/image"
import { blogsQuery } from "@/sanity/lib/queries"
import { sanityFetch } from "@/sanity/lib/sanity-fetch"
import { formatDMY } from "@/utils/helper"
import { SanityDocument } from "next-sanity"

import BlogCard from "./blog-card"

export default async function BlogPag() {


const blogs = await sanityFetch<SanityDocument[]>({ quer

Sanity CMS with Nextjs 19


return (
<section>
<div className="-mx-4 flex flex-wrap text-foreground
<div className="w-full px-4">
<div className="mx-auto mb-[60px] max-w-[550px]
<h1 className="mb-2 block text-3xl font-semibo

<p className="text-body-color text-base">


Here, you&apos;ll find articles on a variety
as well as general topics of interest to our
</p>
</div>
</div>
</div>

<div className="-mx-4 flex flex-wrap">


{blogs && [Link] > 0 ? (
[Link]((blog) => {
return (
<BlogCard
key={blog._id}
slug={[Link]}
date={formatDMY([Link])}
cardTitle={[Link]}
cardDescription={[Link]}
image={getImgUrl([Link], 300, 250)
/>
)
})
) : (
<p>Blogs Not Available!</p>
)}
</div>
</section>
)

Sanity CMS with Nextjs 20


}

const title = "Interio | Blogs"


const description = `Here, you'll find articles on a varie

export const metadata: Metadata = {


title,
description,
// openGraph: {
// title,
// description,
// type: "website",
// url: "[Link]
// siteName: "example",
// images: [
// {
// url: "[Link]
// height: 480,
// width: 480,
// },
// ],
// },
}

add/[[…index]/[Link]

"use client"

/**
* This route is responsible for the built-in authoring en
* All routes under your studio path is handled by this fi
* [Link]
*
* You can learn more about the next-sanity package here:
* [Link]

Sanity CMS with Nextjs 21


*/
import config from "@/[Link]"
import { NextStudio } from "next-sanity/studio"

export default function StudioPage() {


return <NextStudio config={config} />
}

[slug]/[Link]

import Image from "next/image"


import { client } from "@/sanity/lib/client"
import { getImgUrl } from "@/sanity/lib/image"
import {
blogPathsQuery,
blogQuery,
} from "@/sanity/lib/queries"
import { sanityFetch } from "@/sanity/lib/sanity-fetch"
import { formatDMY } from "@/utils/helper"
import { PortableText } from "@portabletext/react"
import { SanityDocument } from "@sanity/client"

import { BlogComponents } from "@/components/custom-ptc"

type PageProps = {
params: {
slug: string
}
}

export async function generateStaticParams() {


const blogs = await [Link](blogPathsQuery)
return blogs
}

Sanity CMS with Nextjs 22


export default async function Page({ params }: PageProps)
const blog = await sanityFetch<SanityDocument>({ query:
// [Link](blog)
if (!blog) {
return (
<main className="container">
<Image src="/[Link]" alt="Not Found Image"
<h1 className="relative -top-10 text-center text-r
</main>
)
}

return (
<main className="prose prose-lg mx-auto text-foregroun
<h1 className="text-foreground">{[Link]}</h1>
{blog?.mainImage && (
<Image
className="my-4 rounded-lg"
src={getImgUrl([Link], 800, 400)}
width={800}
height={400}
alt={blog?.mainImage?.alt || "Blog Image"}
/>
)}
{blog?.body ? <PortableText value={[Link]} compon

{[Link] && (
<section>
<h3 className="m-0">Author</h3>
<div className="flex h-fit items-center">
<Image
src={getImgUrl([Link]?.image, 40, 40)}
width={40}
height={40}
alt={[Link]?.image?.alt}
className="m-0 mr-2 rounded-full"

Sanity CMS with Nextjs 23


/>
<p className="capitalize">{blog?.author?.name}
</div>
<p className="m-0">Published On: {formatDMY(blog
</section>
)}
</main>
)
}

export function formatDMY(date: string, weekday?: boolean)


const options: {
year: string
month: string
day: string
weekday?: string
} = { year: "numeric", month: "long", day: "numeric" }
if (weekday) {
[Link] = "short"
}
// @ts-ignore -d s
return new Date(date).toLocaleDateString(undefined, opti
}

Useful Resources
1. [Link]

2. [Link]

3. [Link]

Sanity CMS with Nextjs 24

You might also like