Postmortem : TypeIt cassé par Mermaid dans le thème LoveIt

TL;DR
L’animation typewriter du thème LoveIt (TypeIt) ne fonctionnait plus sur la home après l’ajout de diagrammes Mermaid dans les posts. Cause : un sélecteur DOM #id-1 partagé entre les deux librairies. Quand Mermaid trouve un bloc orphelin dans un résumé de home, son initialisation crash, et la chaîne d’init JS s’arrête avant d’atteindre TypeIt. Fix : ajouter `
` avant tout bloc Mermaid pour le sortir du résumé. 16 posts touchés au final.
Ce postmortem complète Migration Grav → Hugo où je mentionnais brièvement le bug. Ici, le récit détaillé du diagnostic et la leçon plus large sur les conflits JS dans les thèmes Hugo.
Le symptôme
Sur ma home arleo.eu, sous mon avatar, un texte défile en effet machine à écrire. C’est le typewriter LoveIt, configuré via params.home.profile.typeit dans hugo.toml. Il liste 3 phrases que je trouvais cool :
[params.home.profile]
typeit = true
[params.typeit]
speed = 100
cursorSpeed = 1000
cursorChar = "|"
duration = -1Sur la version FR, ça marchait. Sur la version EN, le texte apparaissait figé. Comme si TypeIt ne se déclenchait pas.
Premier réflexe : devtools navigateur. Console : Uncaught TypeError: Cannot read properties of undefined (reading 'initialize') au chargement de /en/. Stack trace : la fonction qui plante s’appelle mermaid.initialize(). Et juste après, dans la chaîne d’init LoveIt, la fonction initTypeit() n’est jamais appelée.
Donc Mermaid plante au chargement, et ça stoppe l’init JS avant TypeIt. Mais pourquoi Mermaid plante sur la home ? La home n’est pas censée contenir de diagramme.
Le diagnostic
Inspection HTML de la home EN. Dans le résumé d’un post listé sur la home, je trouve un bloc <div class="mermaid"> au beau milieu du résumé d’un article. Comment c’est arrivé là ?
LoveIt génère le résumé d’un post de deux façons :
- Si le post contient
<!--more-->, le résumé = ce qui est avant ce marqueur - Sinon, le résumé = les ~70 premiers mots du contenu
Le post incriminé était /en/posts/postmortem-mcp-timeouts-cloudflare/. Son contenu commence par un paragraphe d’intro, puis directement un diagramme Mermaid. Sans <!--more--> séparateur, LoveIt prend les 70 premiers mots du contenu — qui incluent le bloc Mermaid au format brut.
Mais le rendu HTML du résumé conserve la classe class="mermaid" ET un id="id-1". Donc Mermaid scanne le DOM, voit ce bloc avec class="mermaid", essaie de l’initialiser.
Le crash
Mermaid s’initialise via un script qui :
- Scanne tous les éléments avec
class="mermaid" - Récupère leur contenu textuel
- Le parse comme syntaxe Mermaid
- Génère le SVG correspondant
- Remplace le contenu de l’élément par le SVG
Sur la home, le bloc Mermaid orphelin contient du texte de résumé tronqué qui ressemble à du Mermaid mais qui n’est pas valide. Mermaid lance une exception. Pas catchée. L’init s’arrête.
Et l’init TypeIt, qui est lancée juste après par le même bundle JS de LoveIt, n’est jamais appelée. Donc le typewriter reste muet.
Le symptôme final est très loin de la cause. Pas d’erreur visible côté contenu, pas d’erreur Hugo build, pas d’erreur de syntaxe Mermaid (le diagramme original dans l’article est correct). C’est le résumé HTML qui contient une version corrompue du bloc.
Le fix
Trivial une fois la cause identifiée : ajouter <!--more--> avant le premier bloc Mermaid de chaque post concerné.
---
title: Mon post technique
featuredImage: /images/postmortem-typeit-mermaid-featured.jpg
---
Paragraphe d'introduction qui apparaîtra dans le résumé.
<!--more-->
Le reste du contenu, incluant les blocs Mermaid.Une fois le marqueur ajouté, le résumé devient juste l’intro, sans Mermaid. La home n’a plus de bloc class="mermaid" orphelin. Mermaid n’essaie plus d’initialiser un truc invalide. L’init JS continue jusqu’au bout, TypeIt démarre, le typewriter marche.
L’ampleur
Combien de posts concernés ? Script de détection :
for f in $(find content/posts -name "*.md"); do
if grep -q '```mermaid' "$f"; then
if ! grep -q '<!--more-->' "$f"; then
echo "Manque <!--more--> dans $f"
fi
fi
doneVerdict : 16 fichiers (8 posts FR + 8 EN, vu que tous mes posts récents sont bilingues). Tous corrigés en une passe avec un script Python idempotent.
Pourquoi sur EN et pas FR au début ?
Bonne question. Le bug existait sur les deux langues, mais la home FR avait visiblement TypeIt qui marchait. Inspection plus poussée : c’est l’ordre des posts dans la home qui a fait la différence. Sur FR, les premiers posts n’avaient pas de Mermaid (donc résumés OK). Sur EN, le premier post du listing avait un Mermaid orphelin (donc résumé KO).
LoveIt initialise les composants dans l’ordre où il trouve les éléments DOM. Tant que Mermaid trouve un bloc valide avant un bloc cassé, l’init plante après TypeIt et ne casse rien. Si elle plante avant, elle stoppe la chaîne.
Donc le bug était latent sur FR aussi. Le <!--more--> sur les 16 posts est une vraie correction, pas un patch cosmétique.
La leçon plus large
Les thèmes Hugo qui chargent plusieurs libs JS via un bundle unique ont toutes ce type de risque : une lib qui plante stoppe les autres. Sans try/catch autour de chaque init, on a une chaîne fragile.
Patterns à retenir pour qui écrit un thème Hugo (ou n’importe quel template) :
1. Toujours wrapper les init JS
// Mauvais
mermaid.initialize({startOnLoad: true});
typeit.init('#typeit-target');
// Bon
try { mermaid.initialize({startOnLoad: true}); } catch (e) { console.error('mermaid init failed:', e); }
try { typeit.init('#typeit-target'); } catch (e) { console.error('typeit init failed:', e); }Une init JS n’a aucune raison de propager son erreur aux autres libs du même bundle.
2. Méfiance avec les sélecteurs auto-générés
LoveIt nomme ses éléments id-1, id-2, etc. Si un autre composant utilise les mêmes IDs (ou si Mermaid scanne [class="mermaid"] au lieu de .mermaid:not(.processed)), conflits assurés. Mieux : utiliser des classes/IDs préfixés (.lv-typeit, .lv-mermaid-container).
3. Validation côté Hugo build
On pourrait imaginer un test Hugo qui parse tous les .md contenant des blocs Mermaid, vérifie qu’ils ont un <!--more--> AVANT le premier bloc, et échoue le build sinon. Pas implémenté pour l’instant, mais ça éviterait que le bug réapparaisse en oubliant le marqueur.
4. Tests visuels automatisés
Pour les sites qui dépendent d’effets visuels (typewriter, animations, lazy loading), un test Playwright qui charge la home et vérifie que TypeIt a animé serait précieux. Encore mieux : screenshot diffing.
Pour LoveIt en amont
J’ai ouvert une issue sur le repo LoveIt avec le diagnostic. Le maintainer pourrait :
- Wrapper les init dans des try/catch (fix minimal)
- Stripper les blocs
class="mermaid"du HTML de résumé (fix robuste côté template) - Documenter explicitement que
<!--more-->est requis pour les posts avec Mermaid (fix documentaire)
Le 3ème serait le plus rapide à mettre en place, mais le 2ème reste la solution propre.
Conclusion
Un bug à effet de bord lointain (la home ne typewriter pas) causé par un détail loin du symptôme (un bloc orphelin dans un résumé). Le diagnostic a pris ~30 minutes une fois ouvertes les devtools navigateur. La leçon : quand un comportement JS visible foire, ouvrir la console est la première étape, même si le contenu semble sans rapport.
16 fichiers corrigés en quelques minutes via script. Le marqueur <!--more--> est maintenant systématique dans mes templates de post.
Si tu utilises LoveIt avec Mermaid, vérifie tes posts. Le bug est latent sur n’importe quel post sans marqueur explicite.