Sanity

Real-time collaborative headless CMS. Flexible content management with structured content and Portable Text.

Headless CMSReal-time CollaborationGROQOpen SourceFlexibility
License
MIT
Language
JavaScript/TypeScript
Pricing
無料プランあり
Official Site
Visit Official Site

Headless CMS

Sanity

Overview

Sanity is a headless CMS featuring real-time collaboration and structured content. It offers GROQ (a unique query language), customizable Sanity Studio, and powerful portable text functionality, providing developers with high flexibility while delivering an excellent experience for content editors.

Details

Sanity is designed as a platform for structured content, managing data in structured formats such as portable text, images, and videos. Sanity Studio is an open-source React application that is fully customizable.

Key Features:

  • Real-time Collaboration: Support for simultaneous editing by multiple users
  • GROQ: Graph-Relational Object Queries - powerful query language
  • Portable Text: Flexible and extensible rich text format
  • Sanity Studio: Customizable content management UI
  • Revision History: Complete version management
  • Asset Pipeline: Automatic optimization of images and videos
  • API CDN: Fast delivery through global CDN
  • Plugin System: Extending Studio functionality

Advantages and Disadvantages

Advantages

  • Real-time collaboration features
  • Highly customizable Sanity Studio
  • Powerful query capabilities with GROQ
  • Flexible content management with portable text
  • Complete version history management
  • Excellent developer experience
  • Open source Studio
  • Strong TypeScript support

Disadvantages

  • Steep learning curve (especially GROQ)
  • Cloud hosting is standard
  • Complex initial setup
  • Fewer templates compared to other CMS
  • Relatively small community
  • Self-hosting limitations

Reference Pages

Code Examples

1. Hello World (Basic Setup)

# Creating a Sanity project
npm create sanity@latest

# Configuration options
# Project name: my-sanity-project
# Use default dataset: Yes
# Project output path: /path/to/project
# Select project template: Clean project
# TypeScript: Yes
# Package manager: npm
// schemas/index.ts
export const schemaTypes = [
  {
    name: 'post',
    title: 'Post',
    type: 'document',
    fields: [
      {
        name: 'title',
        title: 'Title',
        type: 'string',
        validation: Rule => Rule.required()
      },
      {
        name: 'slug',
        title: 'Slug',
        type: 'slug',
        options: {
          source: 'title',
          maxLength: 96
        }
      },
      {
        name: 'content',
        title: 'Content',
        type: 'array',
        of: [{type: 'block'}]
      }
    ]
  }
]

// Starting Sanity Studio
// npm run dev

2. Content Management

// Extended schema definition
export default {
  name: 'author',
  title: 'Author',
  type: 'document',
  fields: [
    {
      name: 'name',
      title: 'Name',
      type: 'string',
      validation: Rule => Rule.required()
    },
    {
      name: 'image',
      title: 'Image',
      type: 'image',
      options: {
        hotspot: true
      }
    },
    {
      name: 'bio',
      title: 'Bio',
      type: 'array',
      of: [
        {
          type: 'block',
          styles: [{title: 'Normal', value: 'normal'}],
          lists: []
        }
      ]
    }
  ],
  preview: {
    select: {
      title: 'name',
      media: 'image'
    }
  }
}

// Setting up relations
{
  name: 'author',
  title: 'Author',
  type: 'reference',
  to: {type: 'author'},
  validation: Rule => Rule.required()
}

// Custom block type
{
  type: 'block',
  marks: {
    decorators: [
      {title: 'Strong', value: 'strong'},
      {title: 'Emphasis', value: 'em'},
      {title: 'Code', value: 'code'}
    ],
    annotations: [
      {
        name: 'link',
        type: 'object',
        title: 'URL',
        fields: [
          {
            title: 'URL',
            name: 'href',
            type: 'url'
          }
        ]
      }
    ]
  }
}

3. API Operations

// Setting up Sanity client
import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'production',
  apiVersion: '2023-10-21',
  useCdn: true
})

// GROQ query
const query = `*[_type == "post"]{
  _id,
  title,
  slug,
  author->{
    name,
    image
  },
  content,
  "mainImage": mainImage.asset->url
}`

const posts = await client.fetch(query)

// Query with parameters
const query = `*[_type == "post" && slug.current == $slug][0]{
  title,
  content,
  author->{
    name,
    bio
  }
}`
const params = {slug: 'my-post'}
const post = await client.fetch(query, params)

// Real-time listener
const subscription = client.listen(
  '*[_type == "post"]',
  {},
  {includeResult: true}
).subscribe(update => {
  console.log('Update:', update)
})

// Mutations
client
  .patch('document-id')
  .set({title: 'New Title'})
  .inc({views: 1})
  .commit()
  .then(updatedDoc => {
    console.log('Updated:', updatedDoc)
  })

4. Authentication Setup

// Setting up API token
const client = createClient({
  projectId: 'your-project-id',
  dataset: 'production',
  apiVersion: '2023-10-21',
  token: process.env.SANITY_API_TOKEN, // For write access
  useCdn: false // Disable CDN when using token
})

// Studio authentication configuration
// sanity.config.ts
import {defineConfig} from 'sanity'
import {deskTool} from 'sanity/desk'

export default defineConfig({
  name: 'default',
  title: 'My Sanity Project',
  projectId: 'your-project-id',
  dataset: 'production',
  plugins: [deskTool()],
  schema: {
    types: schemaTypes,
  },
  // Access control
  document: {
    actions: (prev, {schemaType}) => {
      if (schemaType === 'settings') {
        return prev.filter(action => action.action !== 'delete')
      }
      return prev
    }
  }
})

// CORS settings (configured in Sanity management panel)
// Project Settings > API > CORS Origins
// Add http://localhost:3000

5. Plugins and Extensions

// Creating custom plugin
// plugins/myPlugin.js
import {definePlugin} from 'sanity'

export const myPlugin = definePlugin({
  name: 'my-plugin',
  // Adding custom tool
  tools: [
    {
      name: 'my-tool',
      title: 'My Custom Tool',
      component: MyToolComponent
    }
  ],
  // Adding document actions
  document: {
    actions: (prev, context) => {
      return [...prev, {
        label: 'Custom Action',
        onHandle: () => {
          console.log('Custom action triggered')
        }
      }]
    }
  }
})

// Using plugin in sanity.config.ts
import {myPlugin} from './plugins/myPlugin'

export default defineConfig({
  // ...
  plugins: [deskTool(), myPlugin()]
})

// Custom input component
import {useFormValue} from 'sanity'

export function ConditionalField(props) {
  const document = useFormValue([])
  const isVisible = document?.category === 'news'
  
  if (!isVisible) {
    return null
  }
  
  return props.renderDefault(props)
}

// Custom preview
preview: {
  select: {
    title: 'title',
    author: 'author.name',
    media: 'mainImage'
  },
  prepare({title, author, media}) {
    return {
      title,
      subtitle: author && `by ${author}`,
      media
    }
  }
}

6. Deployment and Production Setup

// Integration with Next.js
// lib/sanity.client.ts
import {createClient} from 'next-sanity'

export const client = createClient({
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
  apiVersion: '2023-10-21',
  useCdn: process.env.NODE_ENV === 'production',
})

// Preview functionality
// lib/sanity.preview.ts
import {definePreview} from 'next-sanity/preview'
import {projectId, dataset} from './sanity.client'

const {usePreview} = definePreview({
  projectId,
  dataset,
})

export default usePreview

// pages/api/preview.ts
export default function preview(req, res) {
  res.setPreviewData({})
  res.redirect('/')
}

// Deploying Studio
npm run build
# Deploy with Sanity CLI
sanity deploy

// Environment variables setup
// .env.local
NEXT_PUBLIC_SANITY_PROJECT_ID=your-project-id
NEXT_PUBLIC_SANITY_DATASET=production
SANITY_API_TOKEN=your-token

// Webhook setup (Sanity management panel)
// API > Webhooks > Create Webhook
// URL: https://your-app.com/api/revalidate
// Trigger on: Create, Update, Delete
// Filter: _type == "post"

// ISR implementation
export async function getStaticProps({params}) {
  const post = await client.fetch(
    `*[_type == "post" && slug.current == $slug][0]`,
    {slug: params.slug}
  )

  return {
    props: {post},
    revalidate: 60
  }
}