Contenu

Hugo : gel des diagrammes Mermaid en SVG statiques dark/light

Problème

Les diagrammes Mermaid sur arleo.eu rendaient côté client via cdn.jsdelivr.net. Trois conséquences concrètes :

  1. CSP contraintescript-src cdn.jsdelivr.net et worker-src cdn.jsdelivr.net obligatoires (Mermaid v11 utilise des Web Workers pour ses parsers).
  2. Flash de rendu — le diagramme apparaît après l’exécution JS, créant un décalage visible.
  3. Thème dark ignoré — Mermaid initialisait le SVG en mode clair même quand le thème du site était dark.

La solution : générer les SVG au build avec mmdc, en dehors de tout contexte navigateur.


Architecture : paires dark/light

mmdc (@mermaid-js/mermaid-cli v11.15.0) accepte un flag --theme. On génère systématiquement deux SVG par diagramme :

mmdc -i source.mmd -o diagram-{hash}-dark.svg  --theme dark
mmdc -i source.mmd -o diagram-{hash}-light.svg --theme default

Le shortcode Hugo mermaid-svg sélectionne le bon fichier selon l’attribut theme sur <html> :

{{- $dark  := printf "diagram-%s-dark.svg"  .Get "hash" -}}
{{- $light := printf "diagram-%s-light.svg" .Get "hash" -}}
<img class="mermaid-svg mermaid-dark"  src="{{ $dark  | relURL }}" loading="lazy" />
<img class="mermaid-svg mermaid-light" src="{{ $light | relURL }}" loading="lazy" />

CSS dans _custom.scss :

html[theme=dark]  .mermaid-light { display: none; }
html[theme=light] .mermaid-dark  { display: none; }
html:not([theme]) .mermaid-dark  { display: none; }

Implémentation dans hugo-mcp

La fonction _freeze_mermaid_blocks() dans main.py est appelée automatiquement lors de chaque create_page ou update_page.

Détection : regex sur les blocs ```mermaid.

Hash : SHA256[:8] de la source brute. Même source = même hash entre FR et EN d’un même post — les SVG sont partagés, pas dupliqués.

Marqueur de préservation : le bloc est remplacé par :

<!-- mermaid-source:BASE64_DE_LA_SOURCE -->
{{< mermaid-svg hash="3007aa7f" >}}

Le commentaire mermaid-source encode la source en base64. Lors d’une mise à jour ultérieure, si le hash calculé correspond au hash existant, les SVG ne sont pas régénérés. Si la source a changé, les anciens SVG sont remplacés.

Nettoyage des orphelins : avant de supprimer un SVG devenu inutile, le code scanne tous les fichiers .md du bundle (pas seulement le fichier en cours de traitement). Cela évite de supprimer un SVG encore référencé par la version sœur en cas de diagrammes identiques entre FR et EN.


Bug : &nbsp; dans un sequenceDiagram

Le post postmortem-cf-bot-blocking-mcp contenait :

Note over CF: Custom Rule&nbsp;: host + path + IP range

mmdc échoue sur les entités HTML dans les labels :

Error: Parse error on line 8:
...Note over CF: Custom Rule&nbsp;: host + p
got 'TXT', expected [NEWLINE, 'end', 'COLON' ...]

Fix : nettoyage avant génération :

sed -i 's/&nbsp;/ /g' index.fr.md

Leçon : toujours utiliser des espaces Unicode normaux dans les sources Mermaid, jamais d’entités HTML.


Résultats

  • 12 posts avec des diagrammes Mermaid, versions FR + EN
  • 44 paires SVG générées (dark + light)
  • Certains posts FR/EN partagent les mêmes fichiers SVG (hash identique)
  • Suppression de worker-src cdn.jsdelivr.net et script-src cdn.jsdelivr.net (Mermaid) du CSP
  • Rendu immédiat, sans flash, thème respecté dès le premier paint
  • Vérification Puppeteer : 0 image 404, 0 violation CSP, thème dark correctement appliqué
Diagram Diagram