Crear páginas con bloques

En esta página

Un campo blocks almacena una composición de página ordenada. Cada elemento registra un tipo de bloque, una versión de esquema retenida, una clave estable y los campos declarados por esa versión. Los editores añaden y reordenan bloques en el editor de contenido. La ruta de Astro asigna cada tipo de bloque a un componente.

Definir los tipos de bloque

Define los tipos de bloque antes del campo de colección que los usa. Un seed conserva cada versión numerada y el puntero activo currentVersion.

El siguiente seed define los bloques Hero y Feature grid, y luego los pone a disposición en un campo layout de Pages:

{
  "$schema": "https://emdashcms.com/seed.schema.json",
  "version": "1",
  "blockTypes": [
    {
      "slug": "hero",
      "label": "Hero",
      "category": "Layout",
      "currentVersion": 1,
      "versions": [
        {
          "version": 1,
          "fields": [
            { "slug": "heading", "label": "Heading", "type": "string", "required": true },
            { "slug": "body", "label": "Body", "type": "portableText" },
            { "slug": "image", "label": "Image", "type": "image" },
            { "slug": "link_label", "label": "Link label", "type": "string" },
            { "slug": "link_url", "label": "Link URL", "type": "url" }
          ]
        }
      ]
    },
    {
      "slug": "feature_grid",
      "label": "Feature grid",
      "category": "Layout",
      "currentVersion": 1,
      "versions": [
        {
          "version": 1,
          "fields": [
            { "slug": "heading", "label": "Heading", "type": "string" },
            {
              "slug": "items",
              "label": "Items",
              "type": "repeater",
              "validation": {
                "subFields": [
                  { "slug": "title", "label": "Title", "type": "string", "required": true },
                  { "slug": "description", "label": "Description", "type": "text" }
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "collections": [
    {
      "slug": "pages",
      "label": "Pages",
      "fields": [
        { "slug": "title", "label": "Title", "type": "string", "required": true },
        {
          "slug": "layout",
          "label": "Layout",
          "type": "blocks",
          "validation": {
            "allowedTypes": ["hero", "feature_grid"],
            "maxItems": 20
          }
        }
      ]
    }
  ]
}

El orden de allowedTypes controla el selector de bloques. Si más adelante quitas un tipo permitido, EmDash lo mueve a la lista retiredTypes, gestionada por el servidor. Los bloques existentes siguen siendo editables, pero los editores no pueden añadir ni duplicar ese tipo.

Crear los componentes de Astro

Cada componente recibe value, index y blockKey. El valor incluye _type, _version y _key, de modo que un componente pueda acotar las versiones retenidas cuando el esquema de su bloque evolucione.

El componente Hero lee cada valor mostrado a partir del bloque almacenado:

---
import { sanitizeHref } from "emdash";
import { Image, PortableText, type BlockComponentProps } from "emdash/ui";
import type { PageLayoutBlock } from "../../../emdash-env";

type HeroBlock = Extract<PageLayoutBlock, { _type: "hero" }>;
type Props = BlockComponentProps<HeroBlock>;

const { value } = Astro.props;
---

<section class="hero">
  <div>
    <h1>{value.heading}</h1>
    {value.body && <PortableText value={value.body} />}
    {value.link_url && <a href={sanitizeHref(value.link_url)}>{value.link_label}</a>}
  </div>
  {value.image && <Image image={value.image} />}
</section>

Crea un componente para cada tipo permitido. El componente controla el marcado y los estilos; el valor del bloque aporta el contenido y los medios.

Renderizar la composición

Usa defineBlockComponents para exigir un componente por cada _type de la unión de campos generada. Pasa ese mapa a <Blocks> en la ruta de la página.

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { Blocks, defineBlockComponents } from "emdash/ui";
import type { PageLayoutBlock } from "../../../emdash-env";
import FeatureGrid from "../../components/blocks/FeatureGrid.astro";
import Hero from "../../components/blocks/Hero.astro";

const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.rewrite("/404");

const { entry: page, cacheHint } = await getEmDashEntry("pages", slug);
if (!page) return Astro.rewrite("/404");
Astro.cache.set(cacheHint);

const components = defineBlockComponents<PageLayoutBlock>({
  hero: Hero,
  feature_grid: FeatureGrid,
});
---

<Blocks value={page.data.layout} components={components} />

<Blocks> no realiza consultas de contenido, esquema, medios ni red. Renderiza el array suministrado en el orden almacenado. Un componente de bloque puede hacer una consulta explícita de la aplicación cuando necesite otros datos.

Gestionar un componente que falta

Durante el desarrollo, un tipo sin asignar produce un marcador de posición visible y una advertencia en la consola. El marcador de posición nombra el _type, pero no imprime el valor del bloque almacenado.

En producción, un tipo sin asignar renderiza el componente fallback cuando se proporciona. De lo contrario, no produce ninguna salida:

---
import MissingBlock from "../../components/blocks/MissingBlock.astro";
---

<Blocks value={page.data.layout} components={components} fallback={MissingBlock} />

Publica el soporte del renderizador antes de habilitar o activar un tipo de bloque que use el contenido de producción.

Cambiar el esquema de un bloque

Los cambios compatibles modifican la versión activa. Añadir un campo opcional, añadir un valor predeterminado o relajar la validación mantiene el mismo número de versión. Los bloques almacenados reciben los valores predeterminados la próxima vez que se escriban.

Un cambio incompatible crea una versión inactiva. Eliminar un campo, cambiar el tipo de un campo, añadir un campo obligatorio o restringir la validación son cambios incompatibles.

  1. Crea la versión incompatible mediante la API de esquemas o MCP. Mantenla inactiva.

  2. Actualiza el renderizador para que gestione tanto la versión retenida como la nueva. Despliega el renderizador.

  3. Activa la nueva versión. Los bloques nuevos la usan tras la activación.

  4. Migra explícitamente los bloques almacenados con migrateBlocks: true. Conserva el _key de cada bloque mientras cambias _version y sus campos específicos de la versión.

Las versiones antiguas siguen disponibles para las revisiones, los borradores, el seguimiento de medios y el contenido almacenado. Los tipos de bloque y las versiones retenidas no tienen una operación de eliminación definitiva.

Campos anidados compatibles

Las definiciones de bloque admiten string, text, url, number, integer, boolean, datetime, select, multiSelect, portableText, image, file y repeater.

Dentro de una definición de bloque no se admiten referencias, JSON, slugs, bloques anidados, widgets personalizados, índices físicos, unicidad ni localización por subcampo. Un campo blocks no puede ser obligatorio, único, buscable, indexado ni tener un widget de campo personalizado.

Para conocer las reglas exactas de validación de campos y de valores almacenados, consulta la referencia del campo blocks. Para el comportamiento de conflictos y exportación de seeds, consulta Archivos seed.