Maps & Geolocation
Rules
- Mapbox GL JS for rich interactive maps —
react-map-gl for React wrapper
- Leaflet as free alternative —
react-leaflet for React, no API key required for basic tiles
- API key management:
NEXT_PUBLIC_MAPBOX_TOKEN for client-side Mapbox, never expose server-side secrets
- Geocoding: convert addresses to coordinates — Mapbox Geocoding API or OpenStreetMap Nominatim (free)
- Marker clusters: use
supercluster or Mapbox built-in clustering for large marker datasets (100+)
- Store locator pattern: geocode user location → query nearby stores with Haversine/PostGIS → display on map
- Route display: Mapbox Directions API for driving/walking routes, render as GeoJSON LineString layer
- Browser geolocation:
navigator.geolocation.getCurrentPosition() — always handle permission denied gracefully
- Lazy load map component — maps are heavy; use
dynamic(() => import("./Map"), { ssr: false })
Patterns
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));
}
Avoid
- Server-side rendering map components — always load with
ssr: false or "use client"
- Rendering 1000+ markers without clustering — kills performance; use supercluster
- Hardcoding coordinates — geocode addresses at build time or on demand
- Ignoring geolocation permission denial — always provide a fallback (manual address entry)