Pular para o conteúdo
Tecnologia 21 de agosto de 2026 · 10 min

Sanity CMS pra blog headless: setup completo

Sanity é o CMS headless mais flexível do mercado. Schema em TypeScript, queries GROQ potentes e CDN de imagem embutido. Setup completo com Astro e Next.js.

EM

Equipe Máximo do Marketing

Máximo do Marketing

Equipe de marketing pede mudança no layout do blog. Dev vai pra fila do sprint. Duas semanas depois, mudança ainda não saiu. Post que devia ir segunda vai na quinta. Isso é WordPress gerenciando conteúdo junto com código — os dois acoplados, o editor dependendo do dev pra qualquer coisa.

Sanity CMS resolve esse problema na raiz: conteúdo separado do frontend, com API que qualquer framework consome. O time de conteúdo edita pelo Sanity Studio (interface própria), o site consome via API — e publicar conteúdo não precisa de dev. Deploy de código e deploy de post são coisas diferentes e independentes.

Esse artigo cobre o setup completo: desde a conta até integração com Astro ou Next.js, com código funcional em cada etapa.

O que é CMS headless e por que importa

CMS tradicional (WordPress, Ghost, Drupal) une backend e frontend no mesmo sistema. Conteúdo e template vivem juntos. Mudar um afeta o outro. Plugin novo pode quebrar o layout. Atualização de tema pode apagar customizações.

CMS headless separa os dois:

  • Backend (head-less): onde conteúdo é criado e armazenado — no caso, o Sanity Studio
  • Frontend: onde conteúdo é exibido — seu Astro, Next.js, app mobile, o que for

A conexão é via API. O frontend pergunta “me dá os últimos 10 posts com título, slug e resumo” — Sanity devolve JSON. Como você renderiza é problema do frontend.

Resultado prático: mesmo conteúdo alimenta site, app mobile e newsletter. Troca de framework não toca no conteúdo. E a equipe de conteúdo tem interface própria, sem depender de acesso ao servidor.

Se você está construindo site headless com Jamstack, Sanity é a escolha de CMS mais madura e flexível disponível.

Sanity vs os concorrentes

Você vai esbarrar em Contentful, Strapi e TinaCMS. Comparação direta:

CritérioSanityContentfulStrapiTinaCMS
SchemaCódigo TypeScriptInterface gráficaCódigo ou UICódigo
Linguagem de queryGROQ (própria, potente)REST / GraphQLREST / GraphQLGraphQL
Self-hostable❌ (cloud only)❌ (cloud only)
Plano gratuito3 usuários, 100k docsMuito limitadoGrátis (self-hosted)Grátis (self-hosted)
Image CDN embutido
Melhor praEquipe com dev, volume médioEmpresas grandesDev-heavy customizadoBlog simples

O diferencial do Sanity: schema em código TypeScript. Você define estrutura de conteúdo em .ts, versionado no Git. Não existe divergência entre o que o dev configurou e o que o editor está vendo — ambos leem o mesmo arquivo. Refatorar um campo, renomear um tipo, adicionar validação: tudo com Pull Request revisado pelo time.

GROQ (Graph-Relational Object Queries), a linguagem de query própria do Sanity, é outro diferencial. Mais expressiva que REST, mais prática que GraphQL pra queries de conteúdo.

Instalação e configuração inicial

Precisa de Node.js 18+. Roda dentro da pasta do projeto ou em nova pasta:

npm create sanity@latest

O assistente pergunta:

  • Nome do projeto: qualquer nome (ex: meu-blog-studio)
  • Dataset: production (padrão)
  • Package manager: npm ou pnpm
  • Template: “Blog” pra começar com schema pronto, “Clean” se vai criar do zero

Estrutura gerada:

studio/
  sanity.config.ts    ← configuração central
  schemaTypes/
    index.ts          ← exporta todos os schemas
    post.ts           ← schema de post (no template Blog)
    author.ts         ← schema de autor
    category.ts       ← schema de categoria
  package.json

Roda o studio localmente:

cd studio
npm run dev

Abre http://localhost:3333. Login com conta Sanity (gratuita). O Studio já funciona com o schema do template — você pode criar posts ali agora mesmo.

Definindo schemas: o coração do Sanity

Schema é onde você define quais campos um documento tem. Em vez de configurar via interface (Contentful) ou no banco (SQL), Sanity usa TypeScript. Isso significa autocompletar no editor, validação em tempo de compilação e histórico de mudanças no Git.

Schema completo de post de blog:

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

export const postType = defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      title: 'Título',
      type: 'string',
      validation: (rule) => rule.required().min(10).max(100),
    }),
    defineField({
      name: 'slug',
      title: 'Slug (URL)',
      type: 'slug',
      options: { source: 'title', maxLength: 96 },
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'publishedAt',
      title: 'Data de publicação',
      type: 'datetime',
    }),
    defineField({
      name: 'excerpt',
      title: 'Resumo (até 220 chars)',
      type: 'text',
      rows: 3,
      validation: (rule) => rule.max(220),
    }),
    defineField({
      name: 'mainImage',
      title: 'Imagem principal',
      type: 'image',
      options: { hotspot: true }, // recorte inteligente por ponto focal
      fields: [
        defineField({
          name: 'alt',
          title: 'Texto alternativo',
          type: 'string',
          validation: (rule) => rule.required(),
        }),
      ],
    }),
    defineField({
      name: 'categories',
      title: 'Categorias',
      type: 'array',
      of: [{ type: 'reference', to: { type: 'category' } }],
    }),
    defineField({
      name: 'author',
      title: 'Autor',
      type: 'reference',
      to: { type: 'author' },
    }),
    defineField({
      name: 'body',
      title: 'Conteúdo',
      type: 'array',
      of: [
        { type: 'block' },           // rich text padrão (Portable Text)
        { type: 'image', options: { hotspot: true } }, // imagens inline
      ],
    }),
  ],
  preview: {
    select: { title: 'title', author: 'author.name', media: 'mainImage' },
    prepare(sel) {
      return { ...sel, subtitle: sel.author ? `Por ${sel.author}` : '' }
    },
  },
})

Tipos de campo mais usados:

TipoQuando usar
stringTexto curto (título, nome)
textTexto longo sem formatação
slugURL com geração automática
imageUpload com CDN automático
arrayLista de qualquer coisa
referenceRelacionamento com outro documento
blockRich text (Portable Text)
datetimeData e hora
booleanToggle (ex: featured, active)

Validação é embutida: required(), min(), max(), regex, até funções customizadas. Erros aparecem no Studio antes de publicar.

GROQ: a linguagem de query que diferencia o Sanity

GROQ (Graph-Relational Object Queries) é a linguagem de busca do Sanity. Mais expressiva que REST puro, mais simples que GraphQL pra queries de conteúdo.

Listagem de posts publicados, ordem cronológica reversa:

*[_type == "post" && defined(publishedAt)] | order(publishedAt desc) {
  title,
  "slug": slug.current,
  publishedAt,
  excerpt,
  "authorName": author->name,
  "categories": categories[]->title
}

O operador -> resolve referências inline. author->name traz o nome do autor sem uma segunda chamada de API — GROQ faz o join internamente. Isso diferencia bastante de REST onde você faria uma query pra post e outra pra author.

Post individual por slug (com todos os campos):

*[_type == "post" && slug.current == $slug][0] {
  title,
  publishedAt,
  excerpt,
  "image": mainImage {
    "url": asset->url,
    alt
  },
  "author": author->{ name, "imageUrl": image.asset->url },
  "categories": categories[]->title,
  body
}

Paginação simples:

// posts 0-9 (primeira página de 10)
*[_type == "post"] | order(publishedAt desc) [0...10] {
  title,
  "slug": slug.current,
  publishedAt
}

O Sanity tem GROQ Playground online pra testar queries antes de usar no código. Essencial durante desenvolvimento.

Integração com Astro

Astro é o framework mais popular pra sites de marketing headless. Integração com Sanity é direta.

Instala o client:

npm install @sanity/client @sanity/image-url @portabletext/react

Configura o client:

// src/lib/sanity.ts
import { createClient } from '@sanity/client'

export const client = createClient({
  projectId: import.meta.env.SANITY_PROJECT_ID,
  dataset: import.meta.env.SANITY_DATASET ?? 'production',
  apiVersion: '2025-01-01',
  useCdn: true, // CDN pra content publicado (mais rápido)
})

Variáveis no .env:

SANITY_PROJECT_ID=abc123def456
SANITY_DATASET=production

O projectId está em manage.sanity.io → seu projeto → Settings.

Página de listagem de posts:

---
// src/pages/blog/index.astro
import { client } from '../../lib/sanity'

const posts = await client.fetch(`
  *[_type == "post" && defined(publishedAt)] | order(publishedAt desc) {
    title,
    "slug": slug.current,
    publishedAt,
    excerpt,
    "imageUrl": mainImage.asset->url
  }
`)
---

<ul>
  {posts.map(post => (
    <li>
      {post.imageUrl && <img src={post.imageUrl} alt={post.title} />}
      <a href={`/blog/${post.slug}/`}>{post.title}</a>
      <p>{post.excerpt}</p>
    </li>
  ))}
</ul>

Página de post individual com rotas estáticas (geradas no build):

---
// src/pages/blog/[slug].astro
import { client } from '../../lib/sanity'

export async function getStaticPaths() {
  const slugs = await client.fetch(
    `*[_type == "post"] { "slug": slug.current }`
  )
  return slugs.map(p => ({ params: { slug: p.slug } }))
}

const { slug } = Astro.params
const post = await client.fetch(`
  *[_type == "post" && slug.current == $slug][0] {
    title,
    publishedAt,
    body,
    "author": author->name
  }
`, { slug })
---

<h1>{post.title}</h1>
<p>Por {post.author}</p>
<!-- renderiza Portable Text aqui -->

Pra renderizar o Portable Text (rich text do Sanity) no Astro, usa o pacote @portabletext/astro:

npm install @portabletext/astro
---
import { PortableText } from '@portabletext/astro'
---
<PortableText value={post.body} />

Imagens com CDN automático

O Sanity tem CDN de imagem com transformações via parâmetro de URL. Sem Cloudinary, Sharp ou ImageKit.

import imageUrlBuilder from '@sanity/image-url'

const builder = imageUrlBuilder(client)

export function urlFor(source: any) {
  return builder.image(source)
}

// Uso em qualquer componente:
const src = urlFor(post.mainImage)
  .width(800)
  .height(450)
  .format('webp')
  .quality(80)
  .url()

Resultado: URL com imagem redimensionada, convertida pra WebP e comprimida — tudo por parâmetro de URL, sem processamento local. Isso impacta diretamente nas métricas de PageSpeed e Core Web Vitals, especialmente LCP.

O hotspot: true no schema de imagem habilita ponto focal: editor clica na região importante da imagem (o rosto, o produto) e o CDN recorta inteligente pra qualquer proporção sem cortar o que importa.

Deploy do Studio e do frontend

Hospedar o Studio: deploy grátis na hospedagem própria do Sanity:

npx sanity deploy

Gera URL tipo https://meu-blog.sanity.studio. Compartilha com toda a equipe de conteúdo. Acesso por login Sanity — sem precisar de servidor próprio.

Rebuild automático quando conteúdo muda: configura webhook no Sanity Studio que dispara rebuild no Vercel, Netlify ou Railway sempre que um post é publicado ou atualizado:

  1. Sanity Dashboard → API → Webhooks → Add Webhook
  2. URL: seu hook de deploy da Vercel/Netlify
  3. Dataset: production
  4. Triggers: On create, On update, On delete

A cada publicação, a plataforma de hosting recebe sinal e inicia novo build. Conteúdo novo no ar em 30-60 segundos sem nenhuma ação manual de dev.

Quanto custa

PlanoPreçoInclui
Free$03 usuários, 100k documentos, 20 GB assets, 500k requests/mês
Growth$15/mês por projetoUsuários ilimitados, 500k docs, 100 GB assets
Team$90/mêsDatasets múltiplos, features avançadas
EnterpriseCustomSLA, SAML, compliance

Pra maioria dos blogs e sites de marketing: Free serve por muito tempo. 100k documentos cobre anos de conteúdo. 20 GB de assets dá pra centenas de posts com imagem.

Comparando com os concorrentes:

CMSTier básico pagoCusto
Sanity GrowthUsuários ilimitados$15/mês
DatoCMS Basic3 usuários$99/mês
Contentful Basic5 usuários$300/mês
Strapi Cloud Starter1 ambiente$29/mês

Sanity é o mais barato em feature-parity pra equipes pequenas e médias.

Quando não usar Sanity

Sanity exige desenvolvedor pra configurar schemas e integração inicial. Se sua equipe não tem dev, será frustrante.

Considere alternativas se:

  • Sem dev e conteúdo simples → TinaCMS ou Notion-as-CMS
  • Precisa de self-hosted por requisito regulatório → Strapi ou Directus
  • Quer e-commerce integrado ao CMS → Shopify resolve os dois
  • Time usa o CMS como ferramenta de gestão de dados complexos → Airtable ou Notion são mais indicados

Sanity é a escolha certa se:

  • Tem dev (mesmo que somente você)
  • Schema precisa ser flexível com relacionamentos entre tipos
  • Vai consumir conteúdo em mais de um frontend (site + app + email)
  • Quer queries potentes sem o boilerplate do GraphQL
  • Quer image CDN embutido sem serviço extra

Dicas que economizam tempo

1. Tipos compartilhados: cria um tipo seoType com title, description e openGraph, e reutiliza em todos os schemas com {type: 'seo'}. Sem duplicar campos.

2. Preview ao vivo: Sanity tem integração de live preview com Next.js e Astro. O editor vê mudanças antes de publicar, em tempo real, no design real do site. Configuração de 30 minutos, mas elimina o loop “publica, espera build, verifica, edita”.

3. Portable Text customizado: você define blocos customizados dentro do rich text — CTA embutido, card de produto, embed de vídeo, callout. Tudo em um campo body, sem precisar de campo separado pra cada componente especial.

4. Ordernamento por _updatedAt pra cache: queries pra listagem sempre incluem _updatedAt na projeção. Serve como cache busting — quando o conteúdo muda, o timestamp muda, e queries com revalidação baseada em tempo funcionam corretamente.

5. Dataset de staging: no plano Growth cria um dataset staging separado de production. Time de conteúdo produz no staging, você valida, depois publica pra production. Pra blog isso pode ser excessivo, mas pra e-commerce headless vale muito.


A Máximo implementa Sanity CMS integrado ao Astro e Next.js em projetos de site e blog headless. Quer migrar do WordPress sem perder a capacidade de editar conteúdo facilmente? Fala com a gente.

#sanity #cms #headless

Quer aplicar isso na sua empresa?

A gente faz um diagnóstico gratuito e mostra o caminho mais curto pra você crescer com previsibilidade.

Quero meu diagnóstico