Skip to content

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 :

TermeC'est quoi côté UmbracoC'est quoi côté éditeur
PageDocument Type IsElement=false avec une URLUne URL de site (ex: la home, une fiche produit)
Section-blockDocument Type IsElement=true rangé dans Block+Grids/Sections/Une "section" qu'il ajoute dans la page (hero, CTA...)
Sub-blockDocument 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-block

L'é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.

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

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

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

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

CoucheRôleIsElementFolder
PageURL routée, contient une BlockList sectionsfalsePages/
Section-blockUne zone visuelle complète (hero, CTA, listing...)trueBlock+Grids/Sections/
Sub-blockBrique fine utilisée à l'intérieur d'un section-blocktrueBlock+Grids/Base/

BlockList et BlockGrid - où chacun va

EndroitOutilPourquoi
Niveau page (la propriété sections)Umbraco.BlockListEmpilement vertical full-width, pas de layout à exposer
Intérieur d'un section-block multi-zonesUmbraco.BlockGridLe section-block expose volontairement des colonnes/areas
Liste fixe d'items dans un section-blockUmbraco.BlockListFAQ, témoignages, items d'un listing
Contenu rédactionnel long formatRTE avec blocksArticle, 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égorieContenu
Sections génériquesSection-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 - TexteSub-blocks de contenu texte (titleBlock, paragraphBlock, quoteBlock)
Sub-blocks - MediaSub-blocks visuels (imageBlock, videoBlock, galleryBlock)
Sub-blocks - ActionSub-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 :

BesoinSolution
Catalogue lisible, mais le block reste techniquement disponibleUne seule BlockList Data Type partagée + catégories
Le block ne doit pas pouvoir apparaître ailleursUn 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émentConventionExemple
Document Type NameFrançais, lisible côté éditeur"Hero"
Document Type AliascamelCaseheroBlock
Suffixe Page*PagearticlePage
Suffixe Section-block*Block (vit dans Sections/)heroBlock
Suffixe Sub-block*Block (vit dans Base/)titleBlock
Folder PagesPages/
Folder Section-blocksBlock+Grids/Sections/
Folder Sub-blocksBlock+Grids/Base/
Folder CompositionsCompositions/

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 :

CompositionRôle
pageSettingsSEO (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 : Sitemap

Rendu 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

cshtml
@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 :

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 :

cshtml
@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 ?

QuestionSi 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

CoucheVariations standardDétail
PageCultureToujours, sauf site strictement monolingue
Section-blockCultureSi le block contient du texte éditorial (la plupart du temps)
Sub-blockNothing ou CultureNothing 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ômeCauseFix
15 Document Types *Page qui partagent 80% des champsRé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 v13Remplacer par une BlockList sections + section-blocks
Le client ne peut pas ajouter un hero sur la page ContactSection codée en dur sur la homeLe hero doit être un section-block, pas une composition
metaTitle redéclaré sur 3 PagespageSettings non utiliséeComposer pageSettings sur toutes les Pages
BlockGrid au niveau PageCherche trop de flexibilité de layoutBlockList au niveau page, BlockGrid à l'intérieur des blocks qui le justifient
Block géant avec 25 propriétésPas de décompositionSection-block avec un BlockGrid interne + sub-blocks atomiques
Article de blog en BlockList de section-blocksTout en BlockList par défautRTE avec blocks pour le rédactionnel long
Section-block recréé sur chaque projetPas de catalogue partagéRemonter le section-block dans umbraco-base-project
Catalogue "Ajouter une section" en liste plate de 20 entréesPas de catégories sur le Data Type BlockListGrouper en Sections génériques / Section spécifique - <Page> / Sub-blocks - <Type>
Logique if Model.ContentType.Alias == "X" dans la PageVue de page qui pilote l'affichageChaque section-block a sa partial, la page ne fait que GetBlockListHtmlAsync

Contributors

The avatar of contributor named as Clément Favre Clément Favre

Changelog