Ajouter une carte à Next.js

Une carte est une chose côté client. Elle mesure un élément du DOM, crée un contexte WebGL et récupère des tuiles au fil du déplacement. Rien de tout cela ne peut se produire pendant le rendu d'un Server Component, et c'est pourquoi la plupart des guides de cartographie pour Next.js commencent par un import dynamic() et ssr: false.

Vous n'en avez pas besoin. Les deux approches ci-dessous intègrent la frontière client dans le composant, donc une page Server Component affiche la carte directement.

Installation

Deux façons de commencer, et les onglets ci-dessous restent synchronisés sur toute la page.

Registre copie le composant dans votre projet comme arrive n'importe quel composant shadcn. Le fichier vous appartient, il suit vos jetons de design, et les contrôles et fenêtres de MapLibre sont restylés dessus. Il faut un projet avec un components.json.

Paquet installe @unmap/next et importe <Map> depuis celui-ci. Rien n'est copié : le composant évolue donc avec le SDK, et il fonctionne dans tout projet Next.js, avec ou sans shadcn.

npx @unmap/cli add map

La page

// app/page.tsx
import { Map } from "@/components/ui/map";
 
export default function Page() {
  return (
    <main className="h-dvh">
      <Map
        apiKey={process.env.NEXT_PUBLIC_UNMAP_KEY!}
        center={[-114.07, 51.05]}
        zoom={11}
        className="h-full"
      />
    </main>
  );
}

Voilà toute l'intégration. app/page.tsx reste un Server Component : il ne dit jamais "use client" et il n'est pas enveloppé dans dynamic().

La feuille de style, sur la voie du paquet seulement

Le composant copié importe lui-même la feuille de style de MapLibre : la voie du registre n'a donc rien à faire ici. @unmap/next vous en laisse la charge, parce qu'un paquet qui injecte du CSS global est un paquet dont vous ne maîtrisez plus la cascade. Importez-la une fois dans la mise en page racine.

// app/layout.tsx
import type { ReactNode } from "react";
import "maplibre-gl/dist/maplibre-gl.css";
 
export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="fr">
      <body>{children}</body>
    </html>
  );
}

Pourquoi pas de « use client » dans votre page

Les deux composants sont marqués comme frontières client : importer l'un dans un Server Component revient donc à importer n'importe quel autre composant client. Next rend la page environnante sur le serveur, envoie la carte comme îlot client et la monte dans le navigateur.

Cette frontière a une conséquence qu'il vaut la peine de connaître. Les fonctions ne peuvent pas la traverser, donc un rappel comme onReady doit être passé depuis un composant client :

// app/map-with-marker.tsx
"use client";
 
import { Map } from "@unmap/next";
import { maplibregl } from "@unmap/sdk";
 
export default function MapWithMarker() {
  return (
    <Map
      apiKey={process.env.NEXT_PUBLIC_UNMAP_KEY!}
      center={[-114.07, 51.05]}
      zoom={11}
      onReady={(map) => {
        new maplibregl.Marker().setLngLat([-114.07, 51.05]).addTo(map);
      }}
    />
  );
}

Sur la voie du registre, vous n'écririez pas cela du tout : marker-popup fournit <Marker> comme enfant de <Map>, sans aucun rappel.

Où vit la clé

Les variables NEXT_PUBLIC_ sont insérées dans le paquet navigateur, ce qui est correct ici et mérite d'être dit clairement : une clé utilisée par une carte dans un navigateur est lisible par quiconque charge la page. C'est vrai chez tous les fournisseurs de cartes. Restreignez la clé à vos origines de production dans le tableau de bord plutôt que de tenter de la cacher.

# .env.local
NEXT_PUBLIC_UNMAP_KEY=um_live_...

Les appels côté serveur sont différents. Un route handler qui géocode une adresse devrait lire une clé non restreinte depuis une simple variable UNMAP_API_KEY, jamais depuis une variable NEXT_PUBLIC_.

Problèmes courants

La carte est une boîte vide. Le conteneur n'a pas de hauteur. Les deux composants remplissent leur parent et retombent sur un minimum de 320 px : donnez donc une vraie hauteur au parent.

Aucun bouton de zoom ni attribution, sur la voie du paquet. L'import de la feuille de style manque dans la mise en page. Le composant copié s'en charge pour vous.

Une prop de type fonction déclenche une erreur de frontière. Elle est passée depuis un Server Component. Déplacez cette partie dans un fichier commençant par "use client", comme ci-dessus.

Pour aller plus loin