Un champ blocks stocke une composition de page ordonnée. Chaque élément consigne 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éordonnent 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 currentVersion actif.
Le seed suivant définit les blocs Hero et Feature grid, puis les met à disposition dans un champ 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
}
}
]
}
]
}
L’ordre de allowedTypes contrôle le sélecteur de blocs. Si vous retirez plus tard un type autorisé, EmDash le déplace vers la liste retiredTypes, gérée par le serveur. Les blocs existants restent modifiables, mais les éditeurs ne peuvent ni 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 puisse restreindre les versions conservées lorsque le schéma de son bloc évolue.
Le composant Hero lit chaque valeur affichée à partir du bloc stocké :
---
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 le style ; 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 à <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.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’exécute aucune requête de contenu, de schéma, de médias ou réseau. Il affiche le tableau fourni dans l’ordre stocké. 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 du bloc stocké.
En production, un type non associé affiche le composant fallback s’il est fourni. Sinon, il n’affiche rien :
---
import MissingBlock from "../../components/blocks/MissingBlock.astro";
---
<Blocks value={page.data.layout} components={components} fallback={MissingBlock} />
Publiez la prise en charge du moteur de rendu avant d’activer un type de bloc utilisé par le contenu de production.
Modifier le schéma d’un bloc
Les changements compatibles modifient 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 stockés reçoivent les valeurs par défaut la prochaine fois qu’ils sont écrits.
Un changement incompatible crée une version inactive. Supprimer un champ, changer le type d’un champ, ajouter un champ obligatoire ou durcir la validation sont des changements incompatibles.
-
Créez la version incompatible via l’API de schéma ou MCP. Gardez-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 stockés avec
migrateBlocks: true. Conservez le_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 stocké. Les types de blocs et les versions conservées n’ont pas d’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 blocks ne peut pas lui-même être obligatoire, unique, interrogeable, indexé, ni se voir attribuer un widget de champ personnalisé.
Pour les règles exactes de validation des champs et des valeurs stockées, consultez la référence du champ blocks. Pour le comportement des conflits et de l’export des seeds, consultez Fichiers seed.