Skip to Content
FrontendIntegración con Sanity

Integración con Sanity

El frontend consume el contenido de Sanity directamente desde Server Components en tiempo de build. No hay servidor Node en producción — los datos se resuelven durante next build y el output es HTML estático.


Configuración

Variables de entorno

Agregar en apps/web/.env.local:

NEXT_PUBLIC_SANITY_PROJECT_ID=<project-id> NEXT_PUBLIC_SANITY_DATASET=production

El project-id es el mismo que usa el Studio en SANITY_STUDIO_API_PROJECT_ID. Consultar apps/studio/.env.local para obtenerlo.

En producción estas variables deben estar configuradas en la plataforma de deploy (Vercel, Cloudflare Pages, etc.) antes del build.


Cliente (apps/web/lib/sanity.ts)

import { createClient } from "@sanity/client" export const sanityClient = createClient({ projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!, dataset: process.env.NEXT_PUBLIC_SANITY_DATASET ?? "production", apiVersion: "2025-06-03", useCdn: true, })
OpciónValorPor qué
apiVersionfecha fijaSanity versiona su API por fecha — fijar la versión evita cambios inesperados
useCdn: truetrueEl CDN de Sanity acelera las lecturas. Solo usar false para mutaciones o datos en tiempo real

Queries

Todos los queries viven en apps/web/lib/utils.ts e importan sanityClient.

Proyección reutilizable

const eventFields = ` "slug": slug.current, eyebrow, title, description, longDescription, imagePublicId, imageAlt, eventDate, date, dateShort, timeRange, location, stateOrProvince, city, price, priceAmount, mapsEmbedUrl, mapsDirectionsUrl, reservationUrl, itinerary[] { time, activity }, comingSoon `;

La proyección hace dos cosas importantes:

  • "slug": slug.current — aplana el objeto {current: string} de Sanity a un string plano, que es lo que espera EventContent
  • Selección explícita — solo se transfieren los campos necesarios, no el documento completo

getEventsList()

export async function getEventsList(): Promise<EventContent[]> { const events = await sanityClient.fetch<EventContent[]>( `*[_type == "event" && (eventDate >= now() || !defined(eventDate))] | order(eventDate asc) { ${eventFields} }` ); // Eventos sin eventDate (próximamente sin fecha) van al final return [ ...events.filter((e) => e.eventDate != null), ...events.filter((e) => e.eventDate == null), ]; }

El filtro GROQ eventDate >= now() || !defined(eventDate) excluye eventos pasados y conserva los que no tienen fecha confirmada. El post-procesado en JS mueve los eventos sin fecha al final de la lista (en GROQ, los valores null se ordenan antes que los definidos en orden ascendente).

getEventBySlug()

export async function getEventBySlug( slug: string ): Promise<EventContent | null> { return sanityClient.fetch( `*[_type == "event" && slug.current == $slug][0] { ${eventFields} }`, { slug } ); }

Recibe el parámetro slug como variable GROQ ($slug) en lugar de interpolarlo en el string — esto previene inyección y permite que Sanity optimice la query en caché.


Flujo de datos

Sanity CMS (API) │ sanityClient.fetch() ← build time lib/utils.ts ├─ getEventsList() → EventContent[] └─ getEventBySlug() → EventContent | null Server Components (app/page.tsx, app/events/[slug]/page.tsx) ├─ generateStaticParams() → genera rutas estáticas por slug └─ page() → pasa datos como props a los componentes Componentes (EventsSection, EventCard, etc.) │ solo reciben props, no hacen fetch HTML estático en out/

Los datos de Sanity solo se consultan en tiempo de build. En producción no hay ninguna llamada a la API de Sanity desde el navegador.


Agregar queries para nuevos tipos

Al agregar un nuevo tipo de documento en Sanity (ej. post), el patrón a seguir es:

  1. Definir la proyección de campos en utils.ts:
const postFields = ` "slug": slug.current, title, // ... campos del documento `;
  1. Agregar las funciones de query:
export async function getPostsList(): Promise<PostContent[]> { return sanityClient.fetch( `*[_type == "post"] | order(_createdAt desc) { ${postFields} }` ); } export async function getPostBySlug(slug: string): Promise<PostContent | null> { return sanityClient.fetch( `*[_type == "post" && slug.current == $slug][0] { ${postFields} }`, { slug } ); }
  1. Agregar el tipo correspondiente en types/index.ts siguiendo el patrón de EventContent.

Notas sobre static export y Sanity

apps/web usa output: "export" en next.config.ts. Esto implica:

  • Sin ISR ni revalidación en runtime — para actualizar el contenido se requiere un nuevo build
  • Sin Server Components con fetch en runtime — todos los fetches a Sanity ocurren en build
  • useCdn: true es suficiente — no se necesita el token de escritura ni el withCredentials

Si en el futuro se activa SSR (eliminando output: "export"), el cliente puede cambiar a useCdn: false con revalidación para obtener contenido actualizado sin rebuilds.

Last updated on