Un champ blocks enregistre une composition de page ordonnée. Chaque élément contient un type de bloc, une version de schéma conservée, une clé stable et les champs déclarés par cette version. Les éditeurs ajoutent et réorganisent les blocs dans l’éditeur de contenu. La route Astro associe chaque type de bloc à un composant.
Définir les types de blocs
Définissez les types de blocs avant le champ de collection qui les utilise. Un seed conserve chaque version numérotée ainsi que le pointeur actif currentVersion.
Le seed suivant définit les blocs Hero et Feature grid, puis les rend disponibles dans un champ layout de la collection 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’ordre de allowedTypes détermine le sélecteur de blocs. Si vous retirez plus tard un type autorisé, EmDash le déplace dans la liste retiredTypes gérée par le serveur. Les blocs existants restent modifiables, mais les éditeurs ne peuvent plus ajouter ni dupliquer ce type.
Créer les composants Astro
Chaque composant reçoit value, index et blockKey. La valeur inclut _type, _version et _key, de sorte qu’un composant peut distinguer les versions conservées lorsque le schéma de son bloc évolue.
Le composant Hero lit chaque valeur affichée depuis le bloc enregistré :
---
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>
Créez un composant pour chaque type autorisé. Le composant contrôle le balisage et les styles ; la valeur du bloc fournit le contenu et les médias.
Afficher la composition
Utilisez defineBlockComponents pour exiger un composant pour chaque _type de l’union de champs générée. Passez cette table de correspondance à <Blocks> dans la route de la page.
---
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’effectue aucune requête de contenu, de schéma, de médias ou réseau. Il affiche le tableau fourni dans l’ordre enregistré. Un composant de bloc peut effectuer une requête applicative explicite lorsqu’il a besoin d’autres données.
Gérer un composant manquant
Pendant le développement, un type non associé produit un espace réservé visible et un avertissement dans la console. L’espace réservé indique le _type mais n’affiche pas la valeur enregistrée du bloc.
En production, un type non associé affiche le composant fallback s’il est fourni. Sinon, il ne produit aucune sortie :
---
import MissingBlock from "../../components/blocks/MissingBlock.astro";
---
<Blocks value={page.data.layout} components={components} fallback={MissingBlock} />
Déployez la prise en charge par le moteur de rendu avant d’activer un type de bloc utilisé par du contenu de production.
Modifier le schéma d’un bloc
Les modifications compatibles amendent la version active. Ajouter un champ facultatif, ajouter une valeur par défaut ou assouplir la validation conserve le même numéro de version. Les blocs enregistrés reçoivent les valeurs par défaut lors de leur prochaine écriture.
Une modification incompatible crée une version inactive. Supprimer un champ, changer le type d’un champ, ajouter un champ obligatoire ou restreindre la validation sont des modifications incompatibles.
-
Créez la version incompatible via l’API de schéma ou MCP. Laissez-la inactive.
-
Mettez à jour le moteur de rendu pour qu’il gère à la fois la version conservée et la nouvelle version. Déployez le moteur de rendu.
-
Activez la nouvelle version. Les nouveaux blocs l’utilisent après l’activation.
-
Migrez explicitement les blocs enregistrés avec
migrateBlocks: true. Conservez la_keyde chaque bloc tout en modifiant_versionet ses champs propres à la version.
Les anciennes versions restent disponibles pour les révisions, les brouillons, le suivi des médias et le contenu enregistré. Les types de blocs et les versions conservées ne disposent d’aucune opération de suppression définitive.
Champs imbriqués pris en charge
Les définitions de blocs prennent en charge string, text, url, number, integer, boolean, datetime, select, multiSelect, portableText, image, file et repeater.
Les références, le JSON, les slugs, les blocs imbriqués, les widgets personnalisés, les index physiques, l’unicité et la localisation par sous-champ ne sont pas pris en charge dans une définition de bloc. Un champ de blocs lui-même ne peut pas être obligatoire, unique, consultable par la recherche ou indexé, ni recevoir un widget de champ personnalisé.
Pour les règles exactes de validation des champs et des valeurs enregistrées, consultez la référence du champ blocks. Pour le comportement des conflits et de l’export des seeds, consultez Fichiers seed.