Sanity.io en Producción: El CMS que Más del 90% de Developers Configura Mal
Sanity.io production guide in Spanish: GROQ queries, TypeScript schemas, custom Studio, and real-time preview with Next.js App Router.
La Mayoría de Developers Usa Sanity como si Fuera WordPress
Instalan el studio. Crean cuatro campos. Conectan la API.
*Y se quedan ahí.*
El real problema no es que Sanity.io sea complicado. Es que la mayoría no entiende qué es realmente: un content operating system programable, no un CMS con interfaz bonita.
El real Sanity no es el editor visual. Es el schema-as-code, el GROQ query language y el Content Lake en tiempo real.
Este artículo te enseña a usar Sanity como lo hace un developer de producción.
---
¿Qué es Sanity.io Realmente?
Sanity tiene tres capas que casi nadie separa mentalmente:
→ Content Lake: la base de datos en la nube donde viven tus documentos. Accesible vía API desde cualquier cliente.
→ Sanity Studio: la interfaz de edición. Completamente personalizable en React. No es un admin panel genérico — es una aplicación que tú defines en código.
→ GROQ: el query language propio de Sanity. Más expresivo que GraphQL para contenido. Más predecible que SQL para estructuras anidadas.
Cuando entiendes estas tres capas, empiezas a construir cosas que otros no pueden.
---
Por Qué los Schemas en Sanity son tu Mayor Ventaja
En Contentful o Strapi, defines el schema en la interfaz.
En Sanity, el schema vive en tu repositorio.
```typescript
// schemas/article.ts
import { defineType, defineField } from 'sanity'
export const article = defineType({
name: 'article',
title: 'Artículo',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Título',
type: 'string',
validation: (Rule) => Rule.required().min(10).max(80),
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: {
source: 'title',
maxLength: 96,
},
}),
defineField({
name: 'body',
title: 'Contenido',
type: 'array',
of: [{ type: 'block' }, { type: 'image' }, { type: 'codeBlock' }],
}),
defineField({
name: 'publishedAt',
title: 'Fecha de publicación',
type: 'datetime',
}),
],
})
```
Esto significa que tus schemas están en Git. Puedes hacer code review de cambios de contenido. Puedes tener schemas distintos por rama. Puedes generar tipos TypeScript automáticamente.
❌ Enfoque habitual:
Definir el schema en la interfaz gráfica, exportar JSON, perder el historial de cambios.
✅ Enfoque de producción:
Schema en TypeScript, versionado en Git, tipos generados automáticamente con `sanity typegen generate`.
---
GROQ: El Query Language que Deberías Conocer Antes de GraphQL
GROQ (Graph-Relational Object Queries) es el sistema de consultas de Sanity.
No necesitas configurar resolvers. No necesitas definir un schema GraphQL separado. Escribes queries directamente contra el Content Lake.
```groq
// Todos los artículos publicados con sus autores
*[_type == "article" && defined(publishedAt) && publishedAt < now()] | order(publishedAt desc) {
_id,
title,
slug,
publishedAt,
"author": author->{
name,
"avatar": image.asset->url
},
"estimatedReadingTime": round(length(pt::text(body)) / 5 / 180)
}
```
Esto es GROQ resolviendo una referencia (`author->`), calculando tiempo de lectura en la query y filtrando por fecha. Todo en una sola llamada.
En GraphQL necesitarías configurar resolvers personalizados para el campo calculado. En GROQ, lo escribes en línea.
Uso de GROQ en Next.js App Router
```typescript
// lib/sanity.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: '2024-01-01',
useCdn: true, // true para producción, false para preview
})
// app/blog/page.tsx
import { client } from '@/lib/sanity'
const ARTICLES_QUERY = `
*[_type == "article" && defined(publishedAt)] | order(publishedAt desc) [0...10] {
_id,
title,
"slug": slug.current,
publishedAt,
"authorName": author->name
}
`
export default async function BlogPage() {
const articles = await client.fetch(ARTICLES_QUERY, {}, {
next: { revalidate: 60 } // ISR cada 60 segundos
})
return (
<ul>
{articles.map((article: any) => (
<li key={article._id}>{article.title}</li>
))}
</ul>
)
}
```
Este setup usa ISR (Incremental Static Regeneration) de Next.js con Sanity como fuente. Cada 60 segundos, Next.js revalida el contenido sin necesidad de redeploy.
---
Sanity Studio Personalizado: Donde Está la Ventaja Real
La mayoría instala Sanity Studio con la configuración por defecto.
*Error crítico.*
Sanity Studio es una aplicación React que tú controlas completamente. Puedes añadir componentes personalizados, vistas adicionales, dashboards de métricas.
```typescript
// sanity.config.ts
import { defineConfig } from 'sanity'
import { structureTool } from 'sanity/structure'
import { visionTool } from '@sanity/vision'
import { media } from 'sanity-plugin-media'
import { article } from './schemas/article'
export default defineConfig({
name: 'default',
title: 'Mi Proyecto',
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: 'production',
plugins: [
structureTool({
structure: (S) =>
S.list()
.title('Contenido')
.items([
S.listItem()
.title('Artículos publicados')
.child(
S.documentList()
.title('Publicados')
.filter('_type == "article" && defined(publishedAt)')
),
S.listItem()
.title('Borradores')
.child(
S.documentList()
.title('Borradores')
.filter('_type == "article" && !defined(publishedAt)')
),
]),
}),
visionTool(), // playground de GROQ en el studio
media(), // gestión avanzada de assets
],
schema: {
types: [article],
},
})
```
Con esto, los editores ven dos listas separadas: artículos publicados y borradores. Sin filtros manuales. Sin confusión.
---
Preview en Tiempo Real con Draft Mode
El workflow más importante que configura el 95% de los equipos tarde o mal: live preview.
Sanity tiene `@sanity/preview-kit` para Next.js. El patrón correcto:
```typescript
// app/api/draft/route.ts
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const secret = searchParams.get('secret')
const slug = searchParams.get('slug')
if (secret !== process.env.SANITY_PREVIEW_SECRET) {
return new Response('Token inválido', { status: 401 })
}
draftMode().enable()
redirect(`/blog/${slug}`)
}
```
Cuando el editor hace clic en "Preview" desde Sanity Studio, este endpoint activa el Draft Mode de Next.js. La página se renderiza con datos sin publicar directamente del Content Lake, sin CDN, sin caché.
El editor ve el contenido exactamente como quedará publicado. Sin redeploys. Sin esperas.
---
Los 3 Errores Más Comunes en Producción con Sanity
Error 1: Usar `useCdn: true` en preview
El CDN de Sanity cachea contenido publicado. Si usas `useCdn: true` en modo preview, los borradores no aparecen.
✅ Solución: `useCdn: process.env.NODE_ENV === 'production' && !isDraftMode`
Error 2: No generar tipos TypeScript
Sanity tiene `sanity typegen generate` desde la versión 3.x. Genera tipos automáticamente desde tus schemas.
```bash
npx sanity typegen generate
```
Estos tipos van a `sanity.types.ts`. Úsalos en tus queries. Cero errores de runtime por campos que no existen.
Error 3: Queries sin proyección
❌ Nunca hagas esto:
```groq
*[_type == "article"]
```
✅ Proyecta solo los campos que necesitas:
```groq
*[_type == "article"] { _id, title, "slug": slug.current }
```
Sin proyección, Sanity devuelve el documento completo incluyendo borradores, metadatos internos y campos que no necesitas. El payload puede ser 10x más grande de lo necesario.
---
Sanity vs Contentful vs Strapi: La Decisión Real
El real criterio de elección no es el precio ni las integraciones.
Es quién controla el schema.
→ Contentful: schema en la interfaz. Rápido para equipos no técnicos. Malo para developers que quieren control total.
→ Strapi: self-hosted, schema en código. Bueno si necesitas full control del backend. Malo si no quieres gestionar infraestructura.
→ Sanity.io: schema en código, hosting gestionado, Studio personalizable. *La opción correcta cuando el frontend developer necesita control total sin gestionar servidores.*
Elige Sanity cuando:
El schema cambia frecuentemente y necesitas historial en Git
Necesitas preview en tiempo real sin infraestructura extra
Tu equipo de contenido necesita una interfaz adaptada a su flujo, no genérica
Usas Next.js App Router y quieres ISR nativo
---
Takeaways Clave
→ Schema-as-code: tus schemas en TypeScript, versionados en Git, con tipos generados automáticamente.
→ GROQ sobre GraphQL: para contenido estructurado, GROQ resuelve referencias y calcula campos en una sola query.
→ Studio personalizado: no uses la configuración por defecto. Define vistas, filtros y estructuras adaptadas a tu equipo editorial.
→ Draft Mode nativo: implementa preview en tiempo real desde el primer día, no como afterthought.
→ Proyección siempre: nunca devuelvas documentos completos sin proyección en tus queries GROQ.
*Sanity.io no es el CMS más sencillo de configurar bien. Es el más potente cuando lo configuras correctamente.*
Los equipos que dominan el schema-as-code, GROQ y el Studio personalizado construyen sistemas editoriales que escalan sin fricción. Los que lo usan como un WordPress con API siguen luchando con los mismos problemas que tenían antes.
Lee el artículo completo en brianmenagomez.com


