Um campo blocks armazena uma composição de página ordenada. Cada item registra um tipo de bloco, uma versão de esquema mantida, 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 Astro associa cada tipo de bloco a um componente.
Defina os tipos de bloco
Defina os tipos de bloco antes do campo da coleção que os utiliza. Um seed preserva todas as versões numeradas e o ponteiro ativo currentVersion.
O seed a seguir define os blocos Hero e Feature grid e, em seguida, os disponibiliza em um campo layout da coleção 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 depois, 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 pode distinguir as versões mantidas quando o esquema do bloco evolui.
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.redirect("/404");
const { entry: page, cacheHint } = await getEmDashEntry("pages", slug);
if (!page) return Astro.redirect("/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 nem de 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 espaço reservado visível e um aviso no console. O espaço reservado indica o _type, mas não imprime o valor armazenado do bloco.
Em produção, um tipo não mapeado renderiza o componente fallback, quando fornecido. Caso contrário, não gera 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 por conteúdo de produção.
Altere o esquema de um bloco
Alterações compatíveis modificam a versão ativa. Adicionar um campo opcional, adicionar um valor padrão ou flexibilizar a validação mantém o mesmo número de versão. Os blocos armazenados recebem os valores padrão 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.
-
Crie a versão incompatível pela API de esquema ou pelo MCP. Mantenha-a inativa.
-
Atualize o renderizador para lidar tanto com a versão mantida quanto com a nova versão. Implante o renderizador.
-
Ative a nova versão. Os novos blocos passam a usá-la após a ativação.
-
Migre explicitamente os blocos armazenados com
migrateBlocks: true. Preserve a_keyde cada bloco ao alterar_versione os 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. Tipos de bloco e versões mantidas 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 de blocos em si não pode ser obrigatório, único, pesquisável nem 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.