Le bloc « accordéon FAQ » de la Librairie Miroir fonctionnait sans le moindre accroc sur les postes de développement et sur l’environnement de recette. Une fois déployé en production, où le processus de build appliquait une minification plus agressive des scripts que celle utilisée en recette, deux instances du même bloc sur une page produisaient un comportement erratique : cliquer sur une question ouvrait la réponse de l’autre accordéon, présent plus bas dans la page.
Symptôme : deux blocs, un seul identifiant
Le bloc générait un identifiant unique pour associer chaque question à sa réponse via des attributs aria-controls et id, nécessaires à l’accessibilité de l’accordéon. Cet identifiant était construit à partir d’un compteur de module incrémenté à chaque import du fichier JavaScript du bloc :
let compteur = 0;
function genererIdentifiant() {
compteur += 1;
return `accordeon-${compteur}`;
}
En local, ce module n’était chargé qu’une seule fois par page, le compteur s’incrémentant proprement à chaque instance du bloc. En production, le processus de minification et de regroupement des scripts (bundling) avait fusionné plusieurs points d’entrée en un seul fichier, mais avec une optimisation de tree-shaking qui, dans une configuration spécifique, dupliquait le module dans deux portées différentes du bundle final. Chaque copie du module partait donc de son propre compteur à zéro, réinitialisé indépendamment.
Pourquoi cela ne se voyait pas avant
Aucune erreur JavaScript n’était levée : le code s’exécutait normalement, produisait bien des identifiants, simplement identiques entre deux blocs qui auraient dû en recevoir des différents. Ce type de bug est particulièrement difficile à repérer en recette si l’environnement de recette n’utilise pas exactement la même configuration de minification que la production, ce qui était précisément le cas ici : la recette utilisait un build de développement moins agressif, qui ne déclenchait jamais la duplication de module.

Correctif : ne jamais dépendre d’un état de module partagé pour l’unicité
La correction retenue abandonne le compteur de module au profit de crypto.randomUUID(), disponible nativement dans tous les navigateurs modernes, qui ne dépend d’aucun état partagé entre modules :
function genererIdentifiant() {
return `accordeon-${crypto.randomUUID()}`;
}
Pour un composant construit avec les éléments React de WordPress, useInstanceId du paquet @wordpress/compose reste une alternative pertinente, spécifiquement conçue pour ce cas d’usage à l’intérieur de l’écosystème de blocs :
import { useInstanceId } from '@wordpress/compose';
function Accordeon() {
const instanceId = useInstanceId(Accordeon, 'accordeon');
return <div id={instanceId}>{/* ... */}</div>;
}
useInstanceId génère un identifiant stable par instance de composant au sein du cycle de vie React, sans dépendre d’un compteur de module externe susceptible d’être dupliqué par le processus de build.
Reproduire le bug en local pour le vérifier
Le correctif ne pouvait être validé qu’en reconstruisant les assets avec exactement la configuration de production, via npm run build (utilisant @wordpress/scripts avec ses réglages de production), plutôt qu’avec npm run start qui sert des fichiers non minifiés en développement. Cette étape, ajoutée depuis à la checklist de déploiement, aurait permis de détecter le bug avant sa mise en production initiale.
- Ne jamais générer un identifiant unique à partir d’un compteur de module partagé entre bundles.
- Préférer
crypto.randomUUID()ouuseInstanceIdselon le contexte, tous deux indépendants du processus de build. - Toujours tester au moins une fois avant déploiement avec la configuration de build exacte de la production, pas seulement celle de développement.
Un identifiant unique qui dépend de l’ordre de chargement des modules n’est pas unique, il est simplement optimiste.
Ce que ce cas ne couvre pas
La configuration générale du processus de build (webpack, réglages de minification, choix des outils) sort du périmètre de cet article, centré sur le comportement du bloc lui-même face à un environnement de production qu’il ne maîtrise pas.
En résumé
Deux blocs partageant le même identifiant après minification ont suffi à casser l’accessibilité de l’accordéon FAQ de la Librairie Miroir en production, alors que tout fonctionnait parfaitement en local. La leçon retenue : un identifiant unique ne doit jamais reposer sur un état partagé dont le comportement dépend du processus de build.