Crie páginas com blocos

Nesta página

Um campo blocks armazena uma composição de página ordenada. Cada item registra um tipo de bloco, uma versão de esquema retida, uma chave estável e os campos declarados por essa versão. Os editores adicionam e reordenam blocos no editor de conteúdo. A rota do Astro mapeia cada tipo de bloco para um componente.

Defina os tipos de bloco

Defina os tipos de bloco antes do campo de coleção que os usa. Um seed preserva cada versão numerada e o ponteiro currentVersion ativo.

O seed a seguir define os blocos Hero e Feature grid e depois os disponibiliza em um 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
          }
        }
      ]
    }
  ]
}

A ordem de allowedTypes controla o seletor de blocos. Se você remover um tipo permitido mais tarde, o EmDash o move para a lista retiredTypes, gerenciada pelo servidor. Os blocos existentes continuam editáveis, mas os editores não podem adicionar nem duplicar esse tipo.

Crie os componentes Astro

Cada componente recebe value, index e blockKey. O valor inclui _type, _version e _key, de modo que um componente possa restringir as versões retidas quando o esquema do seu bloco evoluir.

O componente Hero lê cada valor exibido a partir do bloco armazenado:

---
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>

Crie um componente para cada tipo permitido. O componente controla a marcação e o estilo; o valor do bloco fornece o conteúdo e a mídia.

Renderize a composição

Use defineBlockComponents para exigir um componente para cada _type da união de campos gerada. Passe esse mapa para <Blocks> na rota da 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> não executa consultas de conteúdo, esquema, mídia ou rede. Ele renderiza o array fornecido na ordem armazenada. Um componente de bloco pode fazer uma consulta explícita da aplicação quando precisar de outros dados.

Lide com um componente ausente

Durante o desenvolvimento, um tipo não mapeado produz um placeholder visível e um aviso no console. O placeholder nomeia o _type, mas não imprime o valor do bloco armazenado.

Em produção, um tipo não mapeado renderiza o componente fallback, quando fornecido. Caso contrário, não produz nenhuma saída:

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

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

Publique o suporte do renderizador antes de habilitar ou ativar um tipo de bloco usado pelo conteúdo de produção.

Altere o esquema de um bloco

Alterações compatíveis emendam a versão ativa. Adicionar um campo opcional, adicionar um valor padrão ou afrouxar a validação mantém o mesmo número de versão. Os blocos armazenados recebem os padrões na próxima vez em que forem gravados.

Uma alteração incompatível cria uma versão inativa. Remover um campo, alterar o tipo de um campo, adicionar um campo obrigatório ou restringir a validação são alterações incompatíveis.

  1. Crie a versão incompatível pela API de esquema ou pelo MCP. Mantenha-a inativa.

  2. Atualize o renderizador para tratar tanto a versão retida quanto a nova versão. Faça o deploy do renderizador.

  3. Ative a nova versão. Os novos blocos a usam após a ativação.

  4. Migre explicitamente os blocos armazenados com migrateBlocks: true. Preserve o _key de cada bloco enquanto altera _version e seus campos específicos da versão.

As versões antigas continuam disponíveis para revisões, rascunhos, rastreamento de mídia e conteúdo armazenado. Os tipos de bloco e as versões retidas não têm uma operação de exclusão definitiva.

Campos aninhados suportados

As definições de bloco suportam string, text, url, number, integer, boolean, datetime, select, multiSelect, portableText, image, file e repeater.

Referências, JSON, slugs, blocos aninhados, widgets personalizados, índices físicos, unicidade e localização por subcampo não são suportados dentro de uma definição de bloco. Um campo blocks em si não pode ser obrigatório, único, pesquisável, indexado nem receber um widget de campo personalizado.

Para as regras exatas de validação de campos e de valores armazenados, consulte a referência do campo blocks. Para o comportamento de conflitos e de exportação de seeds, consulte Arquivos seed.