Sanity CMS Integration for Content Management
Sanity is a headless CMS with a fully customizable schema and editor (Sanity Studio). Content is stored in Sanity's cloud infrastructure and delivered via CDN-backed API. It differs from others in content storage format — Portable Text instead of HTML — and the ability to build an editor specific to your project.
Sanity Architecture
- Sanity Content Lake — cloud NoSQL database with versioning, transactions, real-time updates
- Sanity Studio — customizable React editor application, deployable to any host
- GROQ API — proprietary query language (Graph-Relational Object Queries)
-
CDN API — cached queries via
cdn.sanity.iofor production
Project Initialization
npm create sanity@latest -- --project my-project --dataset production --template clean
cd my-project
npm run dev # Studio at http://localhost:3333
To connect to an existing project:
npm install @sanity/client
// lib/sanity.ts
import { createClient } from '@sanity/client';
export const client = createClient({
projectId: 'abc123xyz',
dataset: 'production',
apiVersion: '2024-01-01',
useCdn: true, // true for public content, false for private/fresh
});
Schema Definition
Schema is TypeScript/JavaScript, describing document types and their fields:
// schemas/article.ts
import { defineType, defineField } from 'sanity';
export const article = defineType({
name: 'article',
title: 'Article',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
validation: (Rule) => Rule.required().min(5).max(120),
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: { source: 'title', maxLength: 96 },
}),
defineField({
name: 'cover',
title: 'Cover',
type: 'image',
options: { hotspot: true },
fields: [
{ name: 'alt', type: 'string', title: 'Alt text' },
],
}),
defineField({
name: 'body',
title: 'Content',
type: 'array',
of: [
{ type: 'block' }, // Portable Text
{ type: 'image', options: { hotspot: true } },
{ type: 'code' }, // code block (from sanity-plugin-code-input)
],
}),
defineField({
name: 'publishedAt',
type: 'datetime',
}),
defineField({
name: 'categories',
type: 'array',
of: [{ type: 'reference', to: [{ type: 'category' }] }],
}),
],
preview: {
select: { title: 'title', media: 'cover' },
},
});
GROQ Queries
GROQ is a declarative query language specific to Sanity. More powerful than REST filtering:
// All published articles with category data
*[_type == "article" && defined(publishedAt) && publishedAt <= now()] | order(publishedAt desc) [0..9] {
_id,
title,
slug,
publishedAt,
"cover": cover.asset->url,
"categories": categories[]->{ _id, title, slug }
}
// Execute query
const articles = await client.fetch(
`*[_type == "article"] | order(publishedAt desc) [0..$limit] {
_id, title, slug, publishedAt
}`,
{ limit: 10 }
);
// Specific article by slug
const article = await client.fetch(
`*[_type == "article" && slug.current == $slug][0] {
title,
body,
"author": author->{ name, image }
}`,
{ slug: 'my-article-slug' }
);
Portable Text
Instead of HTML, content is stored as structured JSON:
[
{ "_type": "block", "style": "h2", "children": [{ "_type": "span", "text": "Introduction" }] },
{ "_type": "block", "style": "normal", "children": [
{ "_type": "span", "text": "Regular text with " },
{ "_type": "span", "marks": ["strong"], "text": "bold" },
{ "_type": "span", "text": " word." }
]},
{ "_type": "image", "asset": { "_ref": "image-abc123-800x600-jpg" } }
]
To render in React:
import { PortableText } from '@portabletext/react';
import imageUrlBuilder from '@sanity/image-url';
const builder = imageUrlBuilder(client);
<PortableText
value={article.body}
components={{
types: {
image: ({ value }) => (
<img
src={builder.image(value).width(800).url()}
alt={value.alt ?? ''}
/>
),
},
}}
/>
Real-time Updates (Live Preview)
// next.js: app/[slug]/page.tsx with live preview
import { useLiveQuery } from '@sanity/preview-kit';
const { data: article } = useLiveQuery(initialArticle, articleQuery, { slug });
Sanity GROQ Streaming API sends updates via Server-Sent Events when content changes in Studio — page updates without reload.
Webhooks
Sanity Console → API → Webhooks → Create
URL: https://example.com/api/revalidate
Trigger on: create, update, delete
Filter: _type == "article"
Secret: webhook-secret-key
// Next.js API route for ISR revalidation
export async function POST(req: Request) {
const signature = req.headers.get('sanity-webhook-signature');
// verify signature using @sanity/webhook-toolkit
await revalidatePath('/blog');
return Response.json({ revalidated: true });
}
Image Asset Management
Sanity transforms images on-the-fly via URL parameters:
builder.image(source)
.width(1200)
.height(630)
.fit('crop')
.crop('focalpoint') // hotspot-based crop
.format('webp')
.quality(80)
.url()
Timelines
Setting up Sanity project, schema, Studio, GROQ queries, Next.js integration — 3–5 business days. Live preview, custom Studio plugins, webhooks, custom fields — +3–4 days.







