Karten & Geodaten
Regeln
- Mapbox GL JS für interaktive Karten –
react-map-gl als React-Wrapper
- Leaflet als kostenlose Alternative –
react-leaflet für React, kein API-Schlüssel für Basiskarten nötig
- API-Schlüssel-Verwaltung:
NEXT_PUBLIC_MAPBOX_TOKEN für clientseitiges Mapbox, niemals serverseitige Geheimnisse preisgeben
- Geokodierung: Adressen in Koordinaten umwandeln – Mapbox Geocoding API oder OpenStreetMap Nominatim (kostenlos)
- Marker-Clustering:
supercluster oder Mapbox-eigenes Clustering für große Markerdatensätze (100+) verwenden
- Standortsuche (Store Locator): Benutzerstandort geokodieren → nahegelegene Standorte mit Haversine/PostGIS abfragen → auf Karte anzeigen
- Routendarstellung: Mapbox Directions API für Auto-/Fußwege, als GeoJSON LineString-Layer rendern
- Browser-Geolokalisierung:
navigator.geolocation.getCurrentPosition() – stets freundlich mit verweigerter Berechtigung umgehen
- Kartenkomponente lazy laden – Karten sind schwer;
dynamic(() => import("./Map"), { ssr: false }) verwenden
Muster
import Map, { Marker, NavigationControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
function StoreMap({ stores }: { stores: Store[] }) {
return (
<Map
initialViewState={{ longitude: -122.4, latitude: 37.8, zoom: 11 }}
mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_TOKEN}
mapStyle="mapbox://styles/mapbox/streets-v12"
style={{ width: "100%", height: 400 }}
>
<NavigationControl position="top-right" />
{stores.map((store) => (
<Marker key={store.id} longitude={store.lng} latitude={store.lat}>
<Pin />
</Marker>
))}
</Map>
);
}
function haversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
const R = 6371;
const dLat = ((lat2 - lat1) * Math.PI) / 180;
const dLon = ((lon2 - lon1) * Math.PI) / 180;
const a = Math.sin(dLat / 2) ** 2 + Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) ** 2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
Vermeiden
- Serverseitiges Rendern von Kartenkomponenten – immer mit
ssr: false oder "use client" laden
- 1000+ Marker ohne Clustering rendern – Performance-Einbußen; supercluster verwenden
- Koordinaten hartcodieren – Adressen zur Build-Zeit oder bei Bedarf geokodieren
- Verweigerte Geolokalisierungsberechtigung ignorieren – immer einen Fallback bereitstellen (manuelle Adresseingabe)