Intégration de blocs HTML/JS custom (Leaflet) dans Next.js
📌 Table des matières
- Intégration de blocs HTML/JS custom (Leaflet) dans Next.js
🎯 Contexte
Problème
Afficher des blocs HTML/JS custom (comme des cartes Leaflet) insérés via WordPress dans un site Next.js, sans casser le SSR (Server-Side Rendering) et en évitant les erreurs d'hydratation React.
Contraintes
- Les blocs contiennent du JavaScript (ex:
L.map(...)) qui ne peut pas s'exécuter côté serveur. - Next.js génère des erreurs d'hydratation si du JavaScript est exécuté pendant le SSR.
- Le contenu doit rester compatible avec l'éditeur WordPress (pas de modification nécessaire côté WordPress).
🔧 Solution implémentée
La solution repose sur 3 piliers :
-
Isolation des blocs custom : Remplacement du bloc de la carte par un placeholder unique (
<div data-leaflet-placeholder="true">) dans le contenu WordPress. -
Rendu côté client uniquement : Les blocs custom sont affichés via un composant chargé dynamiquement avec
next/dynamic({ ssr: false }). -
Exécution sécurisée du JavaScript : Le JavaScript des blocs custom est exécuté uniquement dans le navigateur, après le chargement des dépendances (ex: Leaflet).
🏗️ Architecture
graph TD
A[Contenu WordPress] --> B[PostBody.tsx]
B --> C[Extraire le bloc de la carte]
C --> D[Remplacer par un placeholder]
D --> E[Parser avec html-react-parser]
E --> F{Contient un placeholder ?}
F --> Oui| G[LeafletMapBlock.tsx]
F -->|Non| H[Afficher normalement]
G --> I[Afficher côté client]
📂 Fichiers et rôles
| Fichier | Rôle | Emplacement |
|---|---|---|
PostBody.tsx | Parse le contenu WordPress, extrait le bloc de la carte et le remplace par un placeholder. | components/post/ |
LeafletMapBlock.tsx | Affiche le bloc HTML/JS custom (ex: carte Leaflet) côté client et exécute son JavaScript. | components/maps/ |
🔍 Fonctionnement détaillé
1. PostBody.tsx
Fonction extractAndReplaceMapBlock(content: string)
Objectif : Extraire le bloc de la carte depuis le HTML brut reçu de WordPress et le remplacer par un placeholder.
Méthode :
-
Détection du conteneur :
- Cherche un
<div>avec un attributstylecontenantbackground:#f9fafb(style spécifique aux cartes dans WordPress). - Utilise une regex pour localiser le début du bloc :
content.match(/<div style="[^"]*background:#f9fafb[^"]*"[^>]*>/);
- Cherche un
-
Extraction du HTML complet :
- Utilise un compteur de balises
<div>pour trouver la balise fermante</div>correspondante. - Remplace le bloc extrait par un placeholder :
const placeholder = `<div data-leaflet-placeholder="true"></div>`;
- Utilise un compteur de balises
-
Retourne :
modifiedContent: Contenu WordPress avec le bloc remplacé par le placeholder.mapHtml: HTML complet du bloc de la carte (y compris les balises<script>).
Exemple de retour :
{
modifiedContent: '<p>Contenu avant...</p><div data-leaflet-placeholder="true"></div><p>Contenu après...</p>',
mapHtml: '<div style="background:#f9fafb;..."><div id="ag-map-iran-1"></div><script>...</script></div>'
}
Rendu conditionnel
Le contenu modifié est parsé avec html-react-parser. Quand un placeholder est détecté (data-leaflet-placeholder="true"), il est remplacé par :
<LeafletMapBlock html={mapHtml} />
Gestion des embeds
Les embeds (Facebook, YouTube, Twitter, Instagram, Read Also) sont traités séparément pour :
- Éviter les conflits avec les blocs custom.
- Appliquer une logique spécifique (ex: wrapper pour les cookies, gestion des URLs).
2. LeafletMapBlock.tsx
Chargement côté client
- Le composant est marqué avec la directive Next.js 13+ :
"use client";
- Chargé dynamiquement avec
ssr: falsepour désactiver le rendu côté serveur :const LeafletMapBlock = dynamic(() => import("../maps/LeafletMapBlock"),{ ssr: false });
Exécution du JavaScript
-
Exposer Leaflet dans
window:(window as any).L = L; // Rend L accessible globalementPourquoi ? Le script de la carte utilise
L.map(...), qui suppose queLest disponible dans le scope global (window). -
Injection du HTML :
containerRef.current.innerHTML = html; // Injecte le HTML brut -
Exécution des scripts : Les balises
<script>du HTML sont extraites et réexécutées côté client :const scripts = containerRef.current.querySelectorAll("script");scripts.forEach((script) => {if (script.textContent) {const newScript = document.createElement("script");newScript.textContent = script.textContent;document.body.appendChild(newScript);script.remove(); // Évite les doublons}});
⚙️ Configuration requise
1. Installer Leaflet
npm install leaflet @types/leaflet
2. Importer le CSS de Leaflet
Le CSS est importé directement dans LeafletMapBlock.tsx :
import "leaflet/dist/leaflet.css";
🚀 Utilisation
Côté WordPress
Aucune modification nécessaire : Les utilisateurs peuvent continuer à insérer des blocs HTML/JS custom via l'éditeur WordPress.
Exemple de bloc compatible :
<div style="background:#f9fafb;border-radius:14px;box-shadow:0 3px 10px rgba(0,0,0,0.09);padding:1.5rem;">
<div id="ag-map-iran-1" style="height: 400px;"></div>
<script>
const map = L.map('ag-map-iran-1').setView([51.505, -0.09], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
</script>
</div>
Côté Next.js
Aucune action requise : le code détecte automatiquement les blocs avec style="background:#f9fafb" et les affiche via LeafletMapBlock.
✅ Avantages
| Avantage | Explication |
|---|---|
| Compatibilité SSR | Le contenu statique reste généré côté serveur. Le placeholder est identique côté serveur et client. |
| Pas de modification WordPress | Les utilisateurs continuent à éditer normalement. |
| Chargement via npm | Leaflet est installé localement (pas de dépendance externe). |
| Extensible | Fonctionne avec n'importe quel bloc HTML/JS custom. |
| Maintenable | Code modulaire et séparé en composants dédiés. |
| Performant | Seuls les blocs custom sont chargés côté client. |
| Pas d'erreur d'hydratation | Utilisation d'un placeholder compatible avec le SSR. |
⚠️ Limitations et améliorations possibles
| Limitation | Solution possible |
|---|---|
Détection basée sur style="background:#f9fafb" | Mettre à jour la regex dans extractAndReplaceMapBlock si le style change. |
| Sécurité | Utiliser DOMPurify pour sanitizer le HTML avant injection. |
| Performance | Optimiser l'extraction du bloc pour les pages avec beaucoup de contenu. |
| Généricité | Étendre extractAndReplaceMapBlock pour détecter d'autres types de blocs custom (ex: class="custom-block"). |
📦 Dépendances
| Package | Version | Rôle |
|---|---|---|
leaflet | Latest | Bibliothèque de cartes. |
@types/leaflet | Latest | Types TypeScript pour Leaflet. |
html-react-parser | Latest | Parsing du contenu WordPress. |
next | 14+ | Framework React. |
📌 Exemple de bloc WordPress compatible
<div style="background:#f9fafb;border-radius:14px;box-shadow:0 3px 10px rgba(0,0,0,0.09);padding:1.5rem;max-width:100%;box-sizing\:border-box;">
<div id="ag-map-iran-1" style="width:100%;height:430px;border-radius:12px;overflow\:hidden;border:1px solid #e5e7eb;background:#f9fafb;"></div>
<script>
(function () {
var map = L.map('ag-map-iran-1', {
zoomControl: true,
scrollWheelZoom: false,
preferCanvas: true
});
L.tileLayer("https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", {
attribution: "© OpenStreetMap © CARTO",
maxZoom: 18
}).addTo(map);
})();
</script>
</div>