CMS 1.0Próximo release · En desarrollo

Definir campos y componentes

Declara schemas editables y registra componentes React seguros para servidor.

12 min de lectura

Contrato de una definición

Cada componente CMS tiene una identidad estable key@version, un schema serializable, valores iniciales, metadata editorial y un renderer React. El registry generado vincula esos contratos con el código desplegado.

  • key identifica el concepto y debe ser único en el sitio.
  • La versión cambia cuando el documento almacenado deja de ser compatible con el renderer anterior.
  • fields define lo que Platform puede editar y validar.
  • El archivo que exporta la definición debe ser server-safe; mueve la interacción a un Client Component importado por el renderer.

Declarar campos

Usa cms.fields() para mantener schema, labels y defaults en un mismo lugar. Los defaults localizados usan las claves configuradas por defineCmsLocales.

TSTypeScriptSolo lectura
import { cms, type CmsFieldValue } from '@veetcuna/cms-next/schema';
 
export const heroFields = cms.fields({
  eyebrow: cms.richText({
    mode: 'inline',
    label: 'Etiqueta',
    defaultValue: { es: 'Veetcuna CMS', en: 'Veetcuna CMS' },
  }),
  title: cms.richText({
    mode: 'inline',
    label: 'Título',
    defaultValue: { es: 'Contenido gobernado', en: 'Governed content' },
  }),
  image: cms.image({
    label: 'Imagen',
    policy: { allowRelative: true },
    defaultValue: { src: '/hero.webp', alt: 'Equipo trabajando' },
  }),
  align: cms.enum(['left', 'center'], {
    label: 'Alineación',
    defaultValue: 'left',
  }),
});
 
export type HeroContent = CmsFieldValue<typeof heroFields>;

También están disponibles campos de texto, rich text, URL, link, imagen, boolean, enum, objetos y repeaters. Para objetos o filas de repeater reutilizables, define otro cms.fields() y aplica defaults con cms.mapValueToFields() cuando corresponda. Conserva _key estable en cada elemento repetible.

Crear una sección

Una sección es una unidad superior de página. Es dueña de su container y de la composición de filas/columnas; cms.page() decide presencia y orden.

TSTypeScriptSolo lectura
import { CMSField } from '@veetcuna/cms-next/client';
import {
  CMSColumn,
  CMSRow,
  CmsRichText,
  defineCMSSection,
} from '@veetcuna/cms-next/components';
import { heroFields } from './Hero.fields';
 
export const HeroCms = defineCMSSection({
  key: 'website:hero',
  title: 'Hero',
  category: 'Marketing',
  fields: heroFields,
  render: ({ content }) => (
    <CMSRow>
      <CMSColumn span={12}>
        <CMSField path="title" label="Título" value={content.title}>
          <h1>
            <CmsRichText value={content.title} mode="inline" />
          </h1>
        </CMSField>
      </CMSColumn>
    </CMSRow>
  ),
});

CMSField anota el elemento real, no introduce un wrapper visible y solo intercepta la interacción cuando el editor está activo. La ruta debe apuntar al campo dentro del contenido del componente.

Crear un bloque

Un bloque es una pieza insertable dentro de una región o layout permitido. La forma curried conecta el contrato con un componente de presentación tipado:

TSTypeScriptSolo lectura
import { defineCMSBlock } from '@veetcuna/cms-next/components';
import { cms } from '@veetcuna/cms-next/schema';
 
const quoteFields = cms.fields({
  quote: cms.richText({ mode: 'block', label: 'Cita' }),
  author: cms.text({ label: 'Autor' }),
});
 
function QuoteBlock(content: typeof quoteFields.defaultValue) {
  return (
    <blockquote>
      <p>{content.quote}</p>
      <footer>{content.author}</footer>
    </blockquote>
  );
}
 
export const QuoteCmsBlock = defineCMSBlock({
  key: 'website:quote',
  title: 'Cita',
  description: 'Cita con autor.',
  category: 'Contenido',
  layer: 'site',
  fields: quoteFields,
})(QuoteBlock);

Si una sección existente también debe aparecer como bloque insertable, defineCMSBlock(SectionDefinition)(PresentationComponent) reutiliza el mismo contrato sin duplicar schemas.

Reglas de compatibilidad

  • No cambies silenciosamente el significado o tipo de una prop publicada bajo la misma identidad.
  • Mantén el renderer anterior o crea una migración explícita antes de retirar una definición.
  • No edites cms.generated.ts; corrige las exportaciones fuente y vuelve a ejecutar Next.js.
  • Evita definiciones en archivos con "use client". La definición queda en servidor y puede renderizar una isla cliente pequeña.
  • El documento local debe seguir completo: es fallback de producción y semilla para el primer bootstrap.