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.
keyidentifica 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.
fieldsdefine 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.
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.
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:
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.