Aller au contenu principal

Intégration de blocs HTML/JS custom (Leaflet) dans Next.js


📌 Table des matières



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

  1. Isolation des blocs custom : Remplacement du bloc de la carte par un placeholder unique (<div data-leaflet-placeholder="true">) dans le contenu WordPress.

  2. Rendu côté client uniquement : Les blocs custom sont affichés via un composant chargé dynamiquement avec next/dynamic({ ssr: false }).

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

FichierRôleEmplacement
PostBody.tsxParse le contenu WordPress, extrait le bloc de la carte et le remplace par un placeholder.components/post/
LeafletMapBlock.tsxAffiche 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 :

  1. Détection du conteneur :

    • Cherche un <div> avec un attribut style contenant background:#f9fafb (style spécifique aux cartes dans WordPress).
    • Utilise une regex pour localiser le début du bloc :
      content.match(/<div style="[^"]*background:#f9fafb[^"]*"[^>]*>/);
  2. 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>`;
  3. 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: false pour désactiver le rendu côté serveur :
    const LeafletMapBlock = dynamic(
    () => import("../maps/LeafletMapBlock"),
    { ssr: false }
    );

Exécution du JavaScript

  1. Exposer Leaflet dans window :

    (window as any).L = L; // Rend L accessible globalement

    Pourquoi ? Le script de la carte utilise L.map(...), qui suppose que L est disponible dans le scope global (window).

  2. Injection du HTML :

    containerRef.current.innerHTML = html; // Injecte le HTML brut
  3. 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

AvantageExplication
Compatibilité SSRLe contenu statique reste généré côté serveur. Le placeholder est identique côté serveur et client.
Pas de modification WordPressLes utilisateurs continuent à éditer normalement.
Chargement via npmLeaflet est installé localement (pas de dépendance externe).
ExtensibleFonctionne avec n'importe quel bloc HTML/JS custom.
MaintenableCode modulaire et séparé en composants dédiés.
PerformantSeuls les blocs custom sont chargés côté client.
Pas d'erreur d'hydratationUtilisation d'un placeholder compatible avec le SSR.


⚠️ Limitations et améliorations possibles

LimitationSolution 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.
PerformanceOptimiser 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

PackageVersionRôle
leafletLatestBibliothèque de cartes.
@types/leafletLatestTypes TypeScript pour Leaflet.
html-react-parserLatestParsing du contenu WordPress.
next14+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: "&copy; OpenStreetMap &copy; CARTO",
maxZoom: 18
}).addTo(map);
})();
</script>
</div>