DEV Community

Cover image for Intégrer une map avec Leaflet sur un projet Next
MandyTrl
MandyTrl

Posted on

Intégrer une map avec Leaflet sur un projet Next

Ces dernières semaines j'ai travaillé sur un projet qui nécessitait l'intégration d'une carte interactive pour afficher des données géographiques. J'avais déjà utilisé la librairie Leaflet 🍃 lors de ma formation et je me suis donc naturellement tournée vers cette solution simple et flexible.

Pourquoi ne pas utiliser react-leaflet ?

Bien que cette bibliothèque encapsule Leaflet dans des composants React, elle ne répondait pas à mes besoins dans ce projet. Les incompatibilités de versions (React, TS, Next) et la simplicité du setup direct de Leaflet m’ont conduit à cette approche minimaliste.



1. Initialisation de la map 🗺️

Installation de la librairie avec la commande suivante:

yarn add leaflet

Création d'un composant <Map />

  • on importe Leaflet et les hooks nécessaires.
  • on utilise useRef pour attribuer une référence à notre Map, ce qui permet de gérer correctement les instances de cartes et d'éviter les conflits au montage/démontage.
  • on configure la carte avec L.map() : spécifiez les options comme le niveau de zoom, les coordonnées du centre, ou l'activation du zoom au scroll. Pour voir les différentes options à configurer vous pouvez les retrouver sur la documentation ici.
  • on ajoute une tuile personnalisée avec L.tileLayer() sans oublier d'ajouter l'attribution obligatoire pour respecter les droits des auteurs.
  • on gère enfin la suppression des instances de carte au démontage du composant pour éviter des collisions.
"use client"
import { useEffect, useRef } from "react"
import L from "leaflet"

const mapRef = useRef<L.Map | null>(null)

useEffect(() => {
    if (mapRef.current) {
      mapRef.current.remove()
      mapRef.current = null
    }

    const map = L.map("map", {
      center: [48.8566, 2.3522], // coordonnées de Paris
      zoom: 10, 
      scrollWheelZoom: false,
    })

    L.tileLayer("https://tiles.stadiamaps.com/tiles/stamen_toner_lite/{z}/{x}/{y}{r}.png",
      {attribution: "©Stamen Design, ©OpenStreetMap contributors",}).addTo(map)

    mapRef.current = map

    return () => {
        if (mapRef.current) {
            mapRef.current.remove()
            mapRef.current = null
        }
    }

    }, [])

  return <div id="map" className="w-full h-full"></div>
}

leaflet map Stamen's design



2. Gérer les erreurs liées au DOM dans Next.js 🛠️

Comme Leaflet utilise le DOM (Document Object Model) à plusieurs niveaux pour fonctionner (initialisation de la map, des marqueurs, etc..) il est important de le désactiver côté SSR (serveur) afin d'éviter des erreurs de type : ❌ ReferenceError: window is not defined.

Pourquoi "use client" n'est pas suffisant ?

Bien que "use client" force le composant à s'exécuter côté client, cela ne garantit pas que toutes les dépendances suivent cette règle. Par exemple, les imports globaux comme import L from 'leaflet' s'exécutent dès que le fichier est chargé, même avant que le composant soit rendu. De plus, certaines configurations ou dépendances peuvent interférer en chargeant Leaflet côté serveur, ce qui entraîne des erreurs.

✅ La solution pour résoudre ce problème : l'import dynamique

L'import dynamique de Next.js permet de charger un composant uniquement côté client, en désactivant complètement le rendu côté serveur (ssr: false). Cela empêche Leaflet et ses dépendances d'être exécutés là où le DOM n'existe pas.
📄Documentation officielle de Next pour gérer les imports dynamiques.



Dans un nouveau composant client que l'on nomme <MapContainer />, on importe dynamiquement le composant <Map /> comme suit :

"use client"
import dynamic from "next/dynamic"

const Map = dynamic(() => import("./Map").then((mod) => mod.Map), {
    ssr: false,
})

export const MapContainer = () => {
    return (
        <div className="md:flex-1 w-[30rem] h-[30rem] border-2 border-black mb-6">
            <Map />
        </div>
    )
}


Ajout du chargement conditionnel

Même avec { ssr: false }, certains problèmes peuvent surgir si Leaflet est chargé avant que le DOM soit prêt. Pour éviter tout conflit, on peut ajouter une vérification dans le composant Map, au début du useEffect qui initialise la map :

  useEffect(() => {
    if (typeof window === "undefined") {
       return
    }

  if (mapRef.current) {
      mapRef.current.remove()
      mapRef.current = null
    }

...


Charger les styles côté client

Une dernière étape consiste à déplacer l'import du CSS directement dans le fichier global.css (en début de fichier) pour une gestion centralisée:

@import "leaflet/dist/leaflet.css";


3. Ajouter des marqueurs 📌

Ajouter un marqueur simple

const marker = L.marker([48.8566, 2.3522]).addTo(map);
marker.bindPopup("Welcome to Paris 🥖!");


Sauf que.. votre console indiquera sûrement une erreur 404 pour atteindre les fichiers de Leaflet de type :

GET /marker-icon-2x.png 404 in 4113ms
GET /marker-shadow.png 404 in 4106ms

error marker leaflet

Par défaut, Leaflet essaie de charger ses images de marqueurs (marker-icon.png, marker-shadow.png, etc.) depuis un chemin relatif à node_modules, ce qui ne correspond souvent pas à l'organisation d'un projet dans un environnement comme Next.js ou d'autres frameworks modernes.



✅ La solution :

  • on copie/colle les fichiers qui se trouve dans le chemin node_modules/leaflet/dist/images dans un nouveau dossier leaflet que l'on retrouvera dans le dossier 📂 public:

where find marker icon leaflet

  • et on définit clairement les nouveaux chemins d'accès aux images:

    L.Icon.Default.mergeOptions({
           iconRetinaUrl: "./leaflet/marker-icon-2x.png",
       iconUrl: "./fealflet/marker-icon.png",
       shadowUrl: "./leaflet/marker-shadow.png",
    })

default marker leaflet

Mais ma solution préférée reste la personnalisation..

Ajouter un marqueur personnalisé

  • on doit d'abord surcharger les icônes par défaut pour éviter de les générer et de se retrouver avec des erreurs 404.
  • puis on importe notre image, on crée l'icône personnalisé que l'on configure avec L.marker().
  • on ajoute enfin notre marqueur personnalisée à notre map (avec une pop-up si on le souhaite):
   L.Icon.Default.mergeOptions({
           iconRetinaUrl: null,
       iconUrl: null,
       shadowUrl: null,
   })

    const customIcon = L.icon({
       iconUrl: "/map-pin2.png",
       iconSize: [30, 30],
       iconAnchor: [15, 30],
       popupAnchor: [0, -30],
    })

    const marker = L.marker([48.8566, 2.3522], { icon: customIcon}).addTo(map)
    marker.bindPopup("Welcome to Paris 🥖!")

custom marker on leaflet

Vous pouvez ensuite activer la geoloc, ajouter le point de l'utilisateur ou toutes autres données interactives qui vous semblent pertinentes !



4. 🎨 Personnalisation de la map

Personnaliser une carte Leaflet permet de créer une expérience utilisateur cohérente avec votre projet. Nous allons voir: comment changer les tuiles, adapter les boutons de zoom à votre design, et créer des popups uniques.


Jouer avec les tuiles

Les tuiles sont la base visuelle de votre carte. Une tuile bien choisie peut renforcer l'identité visuelle d'un projet.
Vous voulez quelque chose d'original, de plus doux ? Optez pour la tile watercolor de Stadia !
🪄Pour trouver des tuiles gratuites voici deux ressources utiles:


Façonner la map (boutons de zoom, container..)

Dans le fichier 📁 global.css on peut expérimenter pas mal de choses et rendre la carte au plus proche de l'identité visuelle de son projet.
Voici un exemple de ce que l'on peut faire:

/* styles généraux /*
.leaflet-container {
    border-radius: 50%;
    overflow: hidden;
    position: relative;
}

.leaflet-control-container {
    position: absolute;
    top: 11rem;
    left: 1rem;
}

/* styles des boutons liés au zoom /*
.leaflet-control-zoom {
    position: absolute;
    border: none !important;
    top: 10px;
    left: -10px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.leaflet-touch .leaflet-bar a {
    width: 40px;
    height: 40px;
    line-height: 40px;
    border: none !important;
    color: white !important;
    font-size: 22px !important;
    border-radius: 50% !important;
    background: rgba(0, 0, 0, 0.673);
    box-shadow: 0 5px 7px rgba(0, 0, 0, 0.3);
    width: 40px;
    height: 40px;
    cursor: pointer;
    transition: all 0.3s ease;
}

/* styles des boutons au survol de la souris /*
.leaflet-control-zoom-in:hover,
.leaflet-control-zoom-out:hover {
    background: #6f00ffb3 !important;
    color: white !important;
    border-color: #6f00ffb3 !important;
}

beautiful custom map's Leaflet with CSS

Pas mal non ? À noter que j'ai désactivé l'attribution par défaut (ajoutez attributionControl: false, à vos options d'initialisation de map) et que j'en ai rajouté une personnalisée.


Vous avez dit pop-up ?

Pour custom sa popup:

  • on pense à désactiver le style par défaut via le CSS
  • et ensuite on ajoute la nouvelle popup au marqueur
    const customPopup = `<div class="popup-content">
                            Le meilleur 🥐 de Paris est ici !
                         </div>`

const marker = L.marker([48.875335693359375, 2.3433215618133545], {
            icon: customIcon,
        }).addTo(map)
        marker.bindPopup(customPopup, { closeButton: false })

custom popup with leaflet


Et voilà, Vous savez comment intégrer et personnaliser une carte interactive avec Leaflet 😊.

Et sinon, quels autres outils utilisez-vous pour créer des cartes dans vos applications ?

Top comments (0)