Mapbox
"Mapbox: interactive maps, GL JS, geocoding, directions, markers, layers, 3D terrain, React (react-map-gl)"
Mapbox provides a GPU-accelerated, vector-tile-based mapping platform built on open standards. Mapbox GL JS renders maps client-side using WebGL, enabling smooth zooming, rotation, and 3D terrain without server round-trips for every viewport change. The platform separates data (tilesets), styling (Mapbox Studio styles), and interaction (GL JS runtime) so each layer can evolve independently. Prefer vector tiles over raster when you need dynamic styling or interactivity. Use Mapbox's hosted APIs (Geocoding, Directions, Isochrone) for server-grade spatial operations without maintaining your own PostGIS instance. In React apps, use `react-map-gl` as the canonical binding -- it wraps GL JS in a declarative component model while preserving full access to the underlying map instance when you need it.
## Key Points
- **Token security**: Use URL-restriction scoping on public tokens. Keep secret tokens server-side only for the Temporary Token API or private tilesets.
- **Viewport-driven data loading**: Fetch feature data only for the current bounding box using `map.getBounds()` to avoid loading the entire dataset upfront.
- **Style immutability**: Treat Mapbox Studio styles as versioned artifacts. Pin your style URL (e.g., `mapbox://styles/you/abc123`) rather than using mutable `streets-v12` in production.
- **Layer ordering**: Insert data layers before label layers with `map.addLayer(layerDef, "waterway-label")` so map labels remain readable.
- **Image and icon preloading**: Call `map.loadImage()` inside `style.load` before adding symbol layers that reference custom icons.
- **React strict mode**: `react-map-gl` v7+ handles double-mount correctly. Avoid wrapping GL JS instantiation in raw `useEffect` -- use the `<Map>` component instead.
- **Bundle size**: Import only `mapbox-gl` -- avoid pulling in the legacy `mapbox.js` wrapper.
- **Recreating the map on every render**: Never instantiate `new mapboxgl.Map()` inside a render function. Use `react-map-gl`'s `<Map>` component or store the instance in a ref.
- **Mutating state inside map event handlers without batching**: Map events fire at 60fps during interactions. Debounce `moveend` and `zoomend` before updating React state.
- **Embedding access tokens in source control**: Tokens pushed to public repos are scraped within minutes. Use environment variables and CI secrets.
- **Using raster tiles for interactive data**: Raster tiles cannot be queried or styled at runtime. Use vector tiles when you need click-to-inspect or dynamic theming.
- **Loading all GeoJSON features at once for large datasets**: For datasets over 10k features, use tilesets uploaded to Mapbox Studio or a server that emits vector tiles on demand.
## Quick Example
```typescript
// npm install mapbox-gl react-map-gl @types/mapbox-gl
import mapboxgl from "mapbox-gl";
import Map, { Marker, Source, Layer, NavigationControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
```skilldb get maps-geolocation-services-skills/MapboxFull skill: 258 linesMapbox
Core Philosophy
Mapbox provides a GPU-accelerated, vector-tile-based mapping platform built on open standards. Mapbox GL JS renders maps client-side using WebGL, enabling smooth zooming, rotation, and 3D terrain without server round-trips for every viewport change. The platform separates data (tilesets), styling (Mapbox Studio styles), and interaction (GL JS runtime) so each layer can evolve independently. Prefer vector tiles over raster when you need dynamic styling or interactivity. Use Mapbox's hosted APIs (Geocoding, Directions, Isochrone) for server-grade spatial operations without maintaining your own PostGIS instance. In React apps, use react-map-gl as the canonical binding -- it wraps GL JS in a declarative component model while preserving full access to the underlying map instance when you need it.
Setup
Install the core libraries:
// npm install mapbox-gl react-map-gl @types/mapbox-gl
import mapboxgl from "mapbox-gl";
import Map, { Marker, Source, Layer, NavigationControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
Set the access token once at the app root:
const MAPBOX_TOKEN = process.env.NEXT_PUBLIC_MAPBOX_TOKEN!;
// Vanilla GL JS
mapboxgl.accessToken = MAPBOX_TOKEN;
// React — pass as prop
function AppMap() {
return (
<Map
mapboxAccessToken={MAPBOX_TOKEN}
initialViewState={{ longitude: -122.4, latitude: 37.8, zoom: 12 }}
style={{ width: "100%", height: "100vh" }}
mapStyle="mapbox://styles/mapbox/streets-v12"
/>
);
}
Key Techniques
Geocoding with the Search API
async function forwardGeocode(query: string): Promise<[number, number] | null> {
const url = new URL("https://api.mapbox.com/search/geocode/v6/forward");
url.searchParams.set("q", query);
url.searchParams.set("access_token", MAPBOX_TOKEN);
url.searchParams.set("limit", "1");
const res = await fetch(url);
const data = await res.json();
const coords = data.features?.[0]?.geometry?.coordinates;
return coords ? [coords[0], coords[1]] : null;
}
Markers and Popups in React
import { Marker, Popup } from "react-map-gl";
import { useState } from "react";
interface Location {
id: string;
name: string;
lng: number;
lat: number;
}
function LocationMarkers({ locations }: { locations: Location[] }) {
const [selected, setSelected] = useState<Location | null>(null);
return (
<>
{locations.map((loc) => (
<Marker
key={loc.id}
longitude={loc.lng}
latitude={loc.lat}
anchor="bottom"
onClick={(e) => {
e.originalEvent.stopPropagation();
setSelected(loc);
}}
/>
))}
{selected && (
<Popup
longitude={selected.lng}
latitude={selected.lat}
anchor="top"
onClose={() => setSelected(null)}
>
<h3>{selected.name}</h3>
</Popup>
)}
</>
);
}
GeoJSON Source and Layer
function RouteLayer({ geojson }: { geojson: GeoJSON.FeatureCollection }) {
return (
<Source id="route" type="geojson" data={geojson}>
<Layer
id="route-line"
type="line"
paint={{
"line-color": "#3b82f6",
"line-width": 4,
"line-opacity": 0.8,
}}
/>
</Source>
);
}
Directions API
interface DirectionsResult {
geometry: GeoJSON.LineString;
duration: number; // seconds
distance: number; // meters
}
async function getDirections(
origin: [number, number],
destination: [number, number],
profile: "driving" | "walking" | "cycling" = "driving"
): Promise<DirectionsResult | null> {
const coords = `${origin[0]},${origin[1]};${destination[0]},${destination[1]}`;
const url = `https://api.mapbox.com/directions/v5/mapbox/${profile}/${coords}?geometries=geojson&access_token=${MAPBOX_TOKEN}`;
const res = await fetch(url);
const data = await res.json();
const route = data.routes?.[0];
if (!route) return null;
return {
geometry: route.geometry,
duration: route.duration,
distance: route.distance,
};
}
3D Terrain
import { useMap } from "react-map-gl";
function Enable3DTerrain() {
const { current: map } = useMap();
useEffect(() => {
if (!map) return;
map.on("style.load", () => {
map.addSource("mapbox-dem", {
type: "raster-dem",
url: "mapbox://mapbox.mapbox-terrain-dem-v1",
tileSize: 512,
maxzoom: 14,
});
map.setTerrain({ source: "mapbox-dem", exaggeration: 1.5 });
});
}, [map]);
return null;
}
Accessing the Map Instance Directly
import { useMap } from "react-map-gl";
function FlyToButton({ lng, lat }: { lng: number; lat: number }) {
const { current: map } = useMap();
const handleClick = () => {
map?.flyTo({ center: [lng, lat], zoom: 14, duration: 2000 });
};
return <button onClick={handleClick}>Go to location</button>;
}
Clustering Points
function ClusteredPoints({ data }: { data: GeoJSON.FeatureCollection }) {
return (
<Source
id="points"
type="geojson"
data={data}
cluster={true}
clusterMaxZoom={14}
clusterRadius={50}
>
<Layer
id="clusters"
type="circle"
filter={["has", "point_count"]}
paint={{
"circle-color": ["step", ["get", "point_count"], "#51bbd6", 10, "#f1f075", 50, "#f28cb1"],
"circle-radius": ["step", ["get", "point_count"], 20, 10, 30, 50, 40],
}}
/>
<Layer
id="cluster-count"
type="symbol"
filter={["has", "point_count"]}
layout={{
"text-field": ["get", "point_count_abbreviated"],
"text-size": 12,
}}
/>
<Layer
id="unclustered-point"
type="circle"
filter={["!", ["has", "point_count"]]}
paint={{ "circle-color": "#11b4da", "circle-radius": 6 }}
/>
</Source>
);
}
Best Practices
- Token security: Use URL-restriction scoping on public tokens. Keep secret tokens server-side only for the Temporary Token API or private tilesets.
- Viewport-driven data loading: Fetch feature data only for the current bounding box using
map.getBounds()to avoid loading the entire dataset upfront. - Style immutability: Treat Mapbox Studio styles as versioned artifacts. Pin your style URL (e.g.,
mapbox://styles/you/abc123) rather than using mutablestreets-v12in production. - Layer ordering: Insert data layers before label layers with
map.addLayer(layerDef, "waterway-label")so map labels remain readable. - Image and icon preloading: Call
map.loadImage()insidestyle.loadbefore adding symbol layers that reference custom icons. - React strict mode:
react-map-glv7+ handles double-mount correctly. Avoid wrapping GL JS instantiation in rawuseEffect-- use the<Map>component instead. - Bundle size: Import only
mapbox-gl-- avoid pulling in the legacymapbox.jswrapper.
Anti-Patterns
- Recreating the map on every render: Never instantiate
new mapboxgl.Map()inside a render function. Usereact-map-gl's<Map>component or store the instance in a ref. - Mutating state inside map event handlers without batching: Map events fire at 60fps during interactions. Debounce
moveendandzoomendbefore updating React state. - Embedding access tokens in source control: Tokens pushed to public repos are scraped within minutes. Use environment variables and CI secrets.
- Using raster tiles for interactive data: Raster tiles cannot be queried or styled at runtime. Use vector tiles when you need click-to-inspect or dynamic theming.
- Ignoring projection: Mapbox GL JS defaults to Web Mercator. If you render area-based analytics (e.g., choropleth density), account for Mercator distortion at high latitudes or switch to
globeprojection. - Loading all GeoJSON features at once for large datasets: For datasets over 10k features, use tilesets uploaded to Mapbox Studio or a server that emits vector tiles on demand.
Install this skill directly: skilldb add maps-geolocation-services-skills
Related Skills
Google Maps Platform
"Google Maps Platform: Maps JavaScript API, Places, Geocoding, Directions, Street View, React (@vis.gl/react-google-maps)"
HERE Maps
HERE Maps Platform: Maps API for JavaScript, geocoding, routing, isoline routing, fleet telematics, and platform data services
Leaflet
"Leaflet: open-source maps, markers, popups, layers, GeoJSON, plugins, React (react-leaflet), tile providers"
OpenLayers
OpenLayers: high-performance open-source map library with vector tiles, projections, OGC services (WMS/WFS), drawing interactions, and GeoJSON/KML support
OpenStreetMap & Nominatim
OpenStreetMap tile usage and Nominatim geocoding API: forward/reverse geocoding, tile servers, Overpass API queries, and attribution requirements
Radar
"Radar: geofencing, geocoding, trip tracking, address validation, maps, fraud detection, JavaScript SDK"