Creare pagine con i blocchi

In questa pagina

Un campo blocks memorizza una composizione di pagina ordinata. Ogni elemento registra un tipo di blocco, una versione dello schema conservata, una chiave stabile e i campi dichiarati da quella versione. Gli editor aggiungono e riordinano i blocchi nell’editor dei contenuti. La route Astro associa ogni tipo di blocco a un componente.

Definire i tipi di blocco

Definisci i tipi di blocco prima del campo della collezione che li usa. Un seed conserva ogni versione numerata e il puntatore currentVersion attivo.

Il seed seguente definisce i blocchi Hero e Feature grid, quindi li rende disponibili in un campo layout di 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
          }
        }
      ]
    }
  ]
}

L’ordine di allowedTypes controlla il selettore dei blocchi. Se in seguito rimuovi un tipo consentito, EmDash lo sposta nell’elenco retiredTypes, gestito dal server. I blocchi esistenti restano modificabili, ma gli editor non possono aggiungere né duplicare quel tipo.

Creare i componenti Astro

Ogni componente riceve value, index e blockKey. Il valore include _type, _version e _key, quindi un componente può restringere le versioni conservate quando lo schema del suo blocco evolve.

Il componente Hero legge ogni valore visualizzato dal blocco memorizzato:

---
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 per ogni tipo consentito. Il componente controlla il markup e lo stile; il valore del blocco fornisce contenuti e media.

Renderizzare la composizione

Usa defineBlockComponents per richiedere un componente per ogni _type dell’unione di campi generata. Passa quella mappa a <Blocks> nella route della pagina.

---
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> non esegue query di contenuto, schema, media o rete. Renderizza l’array fornito nell’ordine memorizzato. Un componente di blocco può eseguire una query applicativa esplicita quando ha bisogno di altri dati.

Gestire un componente mancante

Durante lo sviluppo, un tipo non mappato produce un segnaposto visibile e un avviso nella console. Il segnaposto riporta il nome del _type ma non stampa il valore del blocco memorizzato.

In produzione, un tipo non mappato renderizza il componente fallback se fornito. Altrimenti non produce alcun output:

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

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

Rilascia il supporto del renderer prima di abilitare o attivare un tipo di blocco usato dai contenuti di produzione.

Modificare lo schema di un blocco

Le modifiche compatibili emendano la versione attiva. Aggiungere un campo facoltativo, aggiungere un valore predefinito o allentare la validazione mantiene lo stesso numero di versione. I blocchi memorizzati ricevono i valori predefiniti la prossima volta che vengono scritti.

Una modifica incompatibile crea una versione inattiva. Rimuovere un campo, cambiare il tipo di un campo, aggiungere un campo obbligatorio o restringere la validazione sono modifiche incompatibili.

  1. Crea la versione incompatibile tramite l’API dello schema o MCP. Mantienila inattiva.

  2. Aggiorna il renderer in modo che gestisca sia la versione conservata sia quella nuova. Distribuisci il renderer.

  3. Attiva la nuova versione. I nuovi blocchi la usano dopo l’attivazione.

  4. Migra esplicitamente i blocchi memorizzati con migrateBlocks: true. Conserva il _key di ogni blocco mentre modifichi _version e i suoi campi specifici della versione.

Le vecchie versioni restano disponibili per revisioni, bozze, tracciamento dei media e contenuti memorizzati. I tipi di blocco e le versioni conservate non hanno un’operazione di eliminazione definitiva.

Campi annidati supportati

Le definizioni dei blocchi supportano string, text, url, number, integer, boolean, datetime, select, multiSelect, portableText, image, file e repeater.

Riferimenti, JSON, slug, blocchi annidati, widget personalizzati, indici fisici, unicità e localizzazione per sottocampo non sono supportati all’interno di una definizione di blocco. Un campo blocks non può essere obbligatorio, univoco, ricercabile, indicizzato né avere un widget di campo personalizzato.

Per le regole esatte sulla validazione dei campi e sui valori memorizzati, consulta il riferimento del campo blocks. Per il comportamento dei conflitti e dell’esportazione dei seed, consulta File seed.