NateCue NateCue.
Tech & Tools

Sanity CMS: Toàn tập Headless CMS thế hệ mới

Headless CMS API-first với GROQ query language, real-time collaboration, Portable Text - so sánh với WordPress, Contentful, Decap, Payload

Sanity CMS: Toàn tập về Headless CMS thế hệ mới


1. Sanity CMS là gì?

Sanity là một Headless CMS API-first - nghĩa là nó chỉ lo phần quản lý & lưu trữ nội dung, còn giao diện hiển thị (frontend) bạn tự xây bằng bất kỳ framework nào (Next.js, Nuxt, SvelteKit, v.v.).

Thành lập năm 2017 tại Oslo, Na Uy. Hiện là một trong những Headless CMS phổ biến nhất trong hệ sinh thái React/Next.js.

3 trụ cột kiến trúc

Content Lake

  • Backend lưu trữ nội dung hosted trên cloud của Sanity (không tự host được).
  • Không phải database thông thường - đây là một document store real-time lưu JSON.
  • Hỗ trợ draft/published riêng biệt, tự động lưu revision history.
  • Real-time sync giữa nhiều editor cùng lúc (như Google Docs).

GROQ (Graph-Relational Object Queries)

Ngôn ngữ query riêng của Sanity, open-source, được thiết kế đặc biệt cho JSON document:

*[_type == "post" && publishedAt < now()] | order(publishedAt desc) [0...10] {
  title,
  slug,
  "authorName": author->name,
  "categoryTitles": categories[]->title,
  body
}
  • Dùng -> để “dereference” (join) sang document khác - giải quyết N+1 problem trong 1 query duy nhất.
  • Hỗ trợ projection, filter, sort, slice, aggregation.

Sanity Studio

  • Giao diện quản lý nội dung - là một React app bạn tự host hoặc deploy lên Sanity hosting.
  • Cấu hình qua sanity.config.ts - toàn bộ bằng TypeScript.
  • Có thể embed thẳng vào Next.js app, không cần deploy riêng.

2. Tính năng nổi bật

Real-time Collaboration Nhiều người edit cùng 1 document, thấy cursor của nhau, conflict được resolve tự động ở field level.

Portable Text Thay vì lưu rich text dưới dạng HTML như WordPress, Sanity lưu dưới dạng structured JSON - render được ra bất kỳ đâu (web, mobile, PDF, email).

Image Pipeline CDN Xử lý ảnh on-the-fly qua URL params - không cần Cloudinary cho basic ops:

  • ?w=800&h=600 - resize
  • ?fm=webp&q=75 - convert format + quality

Tính năng 2024-2026

  • Content Releases: Bundle nhiều thay đổi thành 1 “release”, publish atomic.
  • Visual Editing: Click-to-edit overlay ngay trên frontend.
  • AI Assist: Generate, translate, transform content bằng LLM.
  • TypeGen: Generate TypeScript types từ schema + GROQ queries.

3. Pricing (2025-2026)

TierGiáUsersAPI CDN Requests
Free$0/tháng3500K/tháng
Growth~$15/user/thángUnlimited2.5M included
EnterpriseCustomUnlimitedCustom

Free tier khá generous - nhiều production site nhỏ vẫn chạy ổn.


4. So sánh chi tiết

Sanity vs WordPress

Tiêu chíSanityWordPress
Kiến trúcHeadless, cloud-hostedMonolithic (PHP + MySQL)
Real-time CollabNativeKhông có (lock-based)
Rich TextPortable Text (JSON)TinyMCE/Gutenberg (HTML)
Plugin ecosystem~200+60,000+
Learning curveCao (cần developer setup)Thấp (GUI-based)

Sanity vs Decap CMS

Xem thêm: decap-cms

Tiêu chíSanityDecap CMS
Lưu trữContent Lake (Sanity cloud)Git repo (Markdown/JSON files)
Real-time CollabKhông (merge-based)
Vendor lock-inTrung bìnhKhông (data nằm trong Git)
Chi phíFree tier + paidHoàn toàn miễn phí

Sanity vs Payload CMS

Tiêu chíSanityPayload CMS
ArchitectureCloud (Content Lake)Self-hosted, Next.js native
DatabaseProprietaryMongoDB hoặc PostgreSQL
Vendor lock-inTrung bìnhKhông
PricingFree + paid tiersMiễn phí (OSS)

5. Use Cases

Sanity tỏa sáng khi:

  • Multi-channel delivery - 1 content source feed web + mobile app + kiosk + email.
  • Editorial sites phức tạp - newsroom, tạp chí, cần real-time collab.
  • Next.js + React ecosystem - Sanity là Headless CMS phổ biến nhất trong hệ này.

Sanity không phù hợp khi:

  • Team non-technical không có dev support.
  • Blog đơn giản / brochure site - overkill, dùng Ghost hoặc Markdown.
  • Vendor lock-in là vấn đề nghiêm trọng.

6. Schema Definition (TypeScript)

// schemas/post.ts
import { defineType, defineField } from 'sanity'

export default defineType({
  name: 'post',
  title: 'Blog Post',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      type: 'string',
      validation: (Rule) => Rule.required().max(100),
    }),
    defineField({
      name: 'slug',
      type: 'slug',
      options: { source: 'title' },
    }),
    defineField({
      name: 'body',
      type: 'array',
      of: [{ type: 'block' }, { type: 'image' }], // Portable Text
    }),
  ],
})

Liên kết