Modélisation de contenu Umbraco
Cette doc explique comment on construit les types de contenu sur les nouveaux sites Umbraco v17 chez Spektrum. L'objectif est qu'un dev qui ouvre le back-office d'un nouveau projet retrouve toujours la même logique : très peu de pages, un catalogue de section-blocks que l'éditeur empile lui-même, et une autonomie éditoriale maximale.
Règle d'or
Une page n'est pas un type de contenu, c'est un canevas vide dans lequel l'éditeur empile des sections. Une "section" est un block dans une BlockList, pas un Document Type composé sur la page.
Vocabulaire
Trois termes reviennent dans toute la doc, autant les poser tout de suite :
| Terme | C'est quoi côté Umbraco | C'est quoi côté éditeur |
|---|---|---|
| Page | Document Type IsElement=false avec une URL | Une URL de site (ex: la home, une fiche produit) |
| Section-block | Document Type IsElement=true rangé dans Block+Grids/Sections/ | Une "section" qu'il ajoute dans la page (hero, CTA...) |
| Sub-block | Document Type IsElement=true rangé dans Block+Grids/Base/ | Une brique fine à l'intérieur d'une section |
Section-block et sub-block sont techniquement la même chose
Ce sont tous deux des Document Types IsElement=true consommés par un BlockList ou un BlockGrid. La distinction est purement organisationnelle (folder + intention d'usage) : un section-block est destiné à vivre au niveau page, un sub-block à vivre à l'intérieur d'un autre block. Umbraco ne fait pas la différence.
La hiérarchie en clair
Page (homePage)
└── BlockList "sections"
├── heroBlock <- section-block
├── ctaBlock <- section-block
├── splitSectionBlock <- section-block multi-zones
│ └── BlockGrid "columns"
│ ├── titleBlock <- sub-block
│ └── buttonBlock <- sub-block
└── richTextBlock <- section-blockL'éditeur n'interagit qu'avec les Pages et les section-blocks. Les sub-blocks n'apparaissent que lorsqu'il édite l'intérieur d'un section-block multi-zones.
Le réflexe à désapprendre
Sur les sites v7/v8/v10, on créait un Document Type par type de page : HomePage, ContactPage, AboutPage, chacun avec ses propres champs. Le pattern intermédiaire (template v17 actuel, vu sur certains projets récents) est de composer sur la Page une liste fixe de sections (heroSection, listingSection, etc.) via <Compositions>.
Les deux approches ont le même défaut : chaque fois que le client veut une section qu'il n'a pas eue à l'origine, il faut un dev. Ajouter un hero sur la page Contact ? Migration de schéma. Réorganiser deux sections sur la home ? Toucher au code.
Le pattern qu'on adopte aujourd'hui inverse ça : les sections sont des blocks, l'éditeur les compose lui-même.
La doctrine v17
Page = canevas
Une Page est un Document Type quasi vide. Sa seule propriété de contenu est une BlockList sections. Elle compose pageSettings pour le SEO et c'est tout.
<ContentType Alias="homePage" Level="2">
<Info>
<AllowAtRoot>True</AllowAtRoot>
<Folder>Pages</Folder>
</Info>
<Compositions>
<Composition>pageSettings</Composition>
</Compositions>
<GenericProperties>
<GenericProperty>
<Alias>sections</Alias>
<Type>Umbraco.BlockList</Type>
</GenericProperty>
</GenericProperties>
</ContentType>Pourquoi BlockList et pas BlockGrid au niveau page
Une page est un empilement vertical de sections full-width. BlockList force ce modèle et évite que l'éditeur improvise des layouts à colonnes au niveau page (incohérent d'une page à l'autre, casse la responsive). Si une section a besoin de colonnes, c'est dans son block interne qu'on les modélise.
Section-blocks = ce que l'éditeur empile
Un section-block est un Document Type IsElement=true rangé dans le folder Block+Grids/Sections/. Il représente une zone visuelle complète d'une page : un hero, une bannière CTA, un texte riche, un listing, etc.
Côté éditeur, ça donne ça : il ouvre la page d'accueil, voit un seul champ "Sections" (la BlockList), clique "Ajouter" et choisit dans le catalogue :
[Ajouter une section]
- Hero
- Bannière CTA
- Texte riche
- Listing d'articles
- Témoignages
- Section deux colonnesChaque entrée du catalogue est un Document Type qu'on a créé : heroBlock, ctaBlock, richTextBlock, listingBlock, testimonialsBlock, splitSectionBlock. L'éditeur en ajoute un, remplit ses champs (titre, image, lien...), en ajoute un autre, les réordonne par drag & drop. Aucun dev nécessaire.
<ContentType Alias="heroBlock" Level="3">
<Info>
<IsElement>true</IsElement>
<Folder>Block+Grids/Sections</Folder>
</Info>
<GenericProperties>
<GenericProperty>
<Alias>title</Alias>
<Type>Umbraco.TextBox</Type>
</GenericProperty>
<GenericProperty>
<Alias>image</Alias>
<Type>Umbraco.MediaPicker3</Type>
</GenericProperty>
<GenericProperty>
<Alias>cta</Alias>
<Type>Umbraco.MultiUrlPicker</Type>
</GenericProperty>
</GenericProperties>
</ContentType>Sub-blocks = briques fines
Les sub-blocks vivent dans Block+Grids/Base/ et servent à composer l'intérieur d'un section-block qui a besoin de flexibilité (titre, paragraphe, bouton, etc.). Exemple titleBlock :
<ContentType Alias="titleBlock" Level="3">
<Info>
<IsElement>true</IsElement>
<Folder>Block+Grids/Base</Folder>
</Info>
<GenericProperties>
<GenericProperty>
<Alias>title</Alias>
<Type>Umbraco.TextBox</Type>
</GenericProperty>
<GenericProperty>
<Alias>titleSize</Alias>
<Type>Umbraco.DropDown.Flexible</Type>
</GenericProperty>
</GenericProperties>
</ContentType>Récap des trois couches
| Couche | Rôle | IsElement | Folder |
|---|---|---|---|
| Page | URL routée, contient une BlockList sections | false | Pages/ |
| Section-block | Une zone visuelle complète (hero, CTA, listing...) | true | Block+Grids/Sections/ |
| Sub-block | Brique fine utilisée à l'intérieur d'un section-block | true | Block+Grids/Base/ |
BlockList et BlockGrid - où chacun va
| Endroit | Outil | Pourquoi |
|---|---|---|
Niveau page (la propriété sections) | Umbraco.BlockList | Empilement vertical full-width, pas de layout à exposer |
| Intérieur d'un section-block multi-zones | Umbraco.BlockGrid | Le section-block expose volontairement des colonnes/areas |
| Liste fixe d'items dans un section-block | Umbraco.BlockList | FAQ, témoignages, items d'un listing |
| Contenu rédactionnel long format | RTE avec blocks | Article, page legal, news |
N'utilise pas BlockGrid au niveau page
Mettre une BlockGrid sur la Page laisse l'éditeur composer des layouts arbitraires (3 colonnes ici, areas imbriquées là). Tu perds la cohérence visuelle entre pages, et la responsive devient un cauchemar à styler. Garde BlockList au niveau page, monte la complexité dans les section-blocks qui le justifient.
Quand un section-block a besoin de BlockGrid
Cas typiques : splitSectionBlock avec deux zones (gauche/droite), gridFeaturesBlock avec items en grille responsive, tabsBlock avec areas pour le contenu de chaque onglet. Si le section-block n'a qu'un seul flux interne, n'expose pas de BlockGrid : modélise les propriétés directement sur le block.
Catégories de blocks
Le Data Type d'un BlockList ou d'un BlockGrid laisse organiser les blocks autorisés en catégories (groups). Côté éditeur, ça affiche le catalogue "Ajouter une section" avec des en-têtes au lieu d'une liste plate. Sur un site qui a une dizaine de section-blocks, c'est la différence entre un catalogue lisible et une soupe.
Catégories standard à utiliser
| Catégorie | Contenu |
|---|---|
| Sections génériques | Section-blocks réutilisables sur n'importe quelle page (heroBlock, ctaBlock, richTextBlock, listingBlock) |
| Sections spécifiques - <Page> | Section-blocks pensés pour une page précise (teamBlock pour À propos, pricingBlock pour Tarifs) |
| Sub-blocks - Texte | Sub-blocks de contenu texte (titleBlock, paragraphBlock, quoteBlock) |
| Sub-blocks - Media | Sub-blocks visuels (imageBlock, videoBlock, galleryBlock) |
| Sub-blocks - Action | Sub-blocks d'action (buttonBlock, linkBlock, formBlock) |
Sections spécifiques - quand et comment
Sur la maquette d'une page spécifique (À propos, Contact, Carrières...), il y a souvent une section qui n'a de sens que sur cette page : un bloc équipe sur À propos, un formulaire d'embauche sur Carrières, une carte interactive sur Contact. Tu crées le section-block dédié (teamBlock, careersFormBlock, contactMapBlock) et tu le ranges dans une catégorie "Section spécifique - À propos" dans le Data Type BlockList correspondant.
Restreindre vraiment un section-block à une page
Une catégorie est une convention visuelle : elle organise le catalogue, mais n'empêche pas un éditeur d'ajouter un teamBlock sur la home si la BlockList accepte ce block. Deux options selon le besoin :
| Besoin | Solution |
|---|---|
| Catalogue lisible, mais le block reste techniquement disponible | Une seule BlockList Data Type partagée + catégories |
| Le block ne doit pas pouvoir apparaître ailleurs | Un Data Type BlockList dédié au Document Type de la page |
Par défaut, partir sur le Data Type partagé
La plupart du temps, l'éditeur ne mettra pas un bloc équipe sur la home par bêtise - les catégories suffisent à le guider. Crée un Data Type BlockList dédié uniquement quand le contexte le justifie (ex: page de checkout où aucun section-block libre ne doit pouvoir polluer).
Conventions de nommage
| Élément | Convention | Exemple |
|---|---|---|
| Document Type Name | Français, lisible côté éditeur | "Hero" |
| Document Type Alias | camelCase | heroBlock |
| Suffixe Page | *Page | articlePage |
| Suffixe Section-block | *Block (vit dans Sections/) | heroBlock |
| Suffixe Sub-block | *Block (vit dans Base/) | titleBlock |
| Folder Pages | Pages/ | |
| Folder Section-blocks | Block+Grids/Sections/ | |
| Folder Sub-blocks | Block+Grids/Base/ | |
| Folder Compositions | Compositions/ |
Section-block et sub-block ont le même suffixe *Block
La distinction Section vs Sub est une convention de folder, pas de nommage (cf. Vocabulaire). L'alias seul n'a pas à porter cette info, et un même block pourrait théoriquement servir aux deux usages.
Composition technique : pageSettings
Une seule composition à appliquer à toutes les Pages, jamais redéclarée en propriétés directes :
| Composition | Rôle |
|---|---|
pageSettings | SEO (méta titre/description), Open Graph, sitemap, titre d'onglet |
Ne redéclare jamais ces propriétés
Un dev qui ajoute metaTitle directement sur articlePage parce qu'il ne sait pas que pageSettings existe casse l'uniformité du SEO. Avant d'ajouter une propriété sur une Page, vérifie qu'elle n'est pas déjà couverte par pageSettings.
Les cookies et le marketing ne sont pas au niveau page
La bannière de cookies et les scripts marketing/analytics se configurent au niveau du domaine (sur le node racine du site), pas par page. Une Page n'a aucune raison de re-déclarer ça. Si tu vois cookiesSettings ou marketingSettings composé sur une Page d'un projet existant, c'est un héritage à nettoyer.
Structure de tabs pageSettings
pageSettings impose une organisation visible côté éditeur :
Tab : Paramètres de la page
Group : Onglet du navigateur
Group : SEO
Group : Partage sur les médias sociaux
Group : SitemapRendu côté Razor
La règle : une vue de page n'a aucune logique métier. Elle rend la liste de sections, et c'est tout.
Page
@using Web.umbraco.Models
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<HomePage>
@{
Layout = "~/Views/Layouts/_MasterLayout.cshtml";
}
@if (Model.Sections != null && Model.Sections.Any())
{
@await Html.GetBlockListHtmlAsync(Model.Sections)
}Pourquoi pas de partials de section appelés explicitement
Avant on faisait @await Html.PartialAsync("~/Views/Partials/sections/heroSection.cshtml") dans la page. Avec le pattern actuel, la page ne sait pas quels sections-blocks elle contient. Umbraco route automatiquement chaque section-block vers son partial via Views/Partials/blocklist/Components/<alias>.cshtml.
Section-block
Un section-block a sa partial dans Views/Partials/blocklist/Components/<alias>.cshtml :
@using Web.umbraco.Models
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockListItem>
@{
var block = Model.Content as HeroBlock;
}
<section class="hero">
<h1>@block.Title</h1>
@if (block.Image != null)
{
<img src="@block.Image.Url()" alt="" />
}
</section>Section-block avec BlockGrid interne
Si le section-block expose un BlockGrid (cas multi-zones), il rend ses items :
@using Web.umbraco.Models
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockListItem>
@{
var block = Model.Content as SplitSectionBlock;
}
<section class="split">
@await Html.GetBlockGridItemsHtmlAsync(block.Columns)
</section>Quand créer un nouveau Document Type ?
| Question | Si oui, créer... |
|---|---|
| Logique de routing ou consommation externe spécifique ? | Une nouvelle Page |
| Zone visuelle récurrente, réutilisable sur plusieurs pages ? | Un nouveau section-block rangé en catégorie Sections génériques |
| Section dessinée sur la maquette pour une page précise ? | Un nouveau section-block rangé en catégorie Section spécifique - <Page> |
| Brique fine réutilisée dans plusieurs section-blocks ? | Un nouveau sub-block rangé en catégorie Sub-blocks - <Type> |
| Variante visuelle d'un section-block existant ? | Rien - ajouter une propriété (style, variant) sur le block existant |
| Contenu unique qui tient en texte/image ? | Rien - utiliser richTextBlock ou un section-block générique |
Le piège du "encore une page sur mesure"
Une demande "il me faut une page Équipe" ne justifie pas un teamPage Document Type. Crée un teamBlock (section-block) et range-le dans la catégorie Section spécifique - À propos (ou Sections génériques si tu veux qu'il soit réutilisable). Le jour où le client veut l'afficher ailleurs, il suffit de l'autoriser dans la BlockList ciblée.
Variations de culture
| Couche | Variations standard | Détail |
|---|---|---|
| Page | Culture | Toujours, sauf site strictement monolingue |
| Section-block | Culture | Si le block contient du texte éditorial (la plupart du temps) |
| Sub-block | Nothing ou Culture | Nothing pour un block purement structurel (séparateur, espacement) |
Cohérence verticale obligatoire
Un section-block Culture ne peut pas contenir un sub-block Culture non variant. Si tu mets un sub-block Culture dans un block Nothing, Umbraco refuse la sauvegarde côté éditeur, mais l'erreur est cryptique.
Récap - antipatterns à éviter
| Symptôme | Cause | Fix |
|---|---|---|
15 Document Types *Page qui partagent 80% des champs | Réflexe v8 "une page = un Document Type" | Une seule Page par URL, le contenu en section-blocks |
Page avec compositions heroSection, ctaSection, etc. | Pattern intermédiaire v13 | Remplacer par une BlockList sections + section-blocks |
| Le client ne peut pas ajouter un hero sur la page Contact | Section codée en dur sur la home | Le hero doit être un section-block, pas une composition |
metaTitle redéclaré sur 3 Pages | pageSettings non utilisée | Composer pageSettings sur toutes les Pages |
| BlockGrid au niveau Page | Cherche trop de flexibilité de layout | BlockList au niveau page, BlockGrid à l'intérieur des blocks qui le justifient |
| Block géant avec 25 propriétés | Pas de décomposition | Section-block avec un BlockGrid interne + sub-blocks atomiques |
| Article de blog en BlockList de section-blocks | Tout en BlockList par défaut | RTE avec blocks pour le rédactionnel long |
| Section-block recréé sur chaque projet | Pas de catalogue partagé | Remonter le section-block dans umbraco-base-project |
| Catalogue "Ajouter une section" en liste plate de 20 entrées | Pas de catégories sur le Data Type BlockList | Grouper en Sections génériques / Section spécifique - <Page> / Sub-blocks - <Type> |
Logique if Model.ContentType.Alias == "X" dans la Page | Vue de page qui pilote l'affichage | Chaque section-block a sa partial, la page ne fait que GetBlockListHtmlAsync |

