Imported from gabrielmoreira/agent-skills-mirror (
mirrors/repos/mapbox@mapbox-agent-skills/skills/mapbox-google-maps-migration/AGENTS.md). Install upstream withnpx skills add gabrielmoreira/agent-skills-mirror --skill mapbox-google-maps-migration. Copyright stays with the author.
Google Maps to Mapbox GL JS Migration Guide
Quick reference for migrating from Google Maps Platform to Mapbox GL JS with API equivalents and patterns.
Critical Differences
| Aspect | Google Maps | Mapbox GL JS |
|---|---|---|
| Coordinates | {lat, lng} objects |
[lng, lat] arrays |
| Philosophy | Imperative (objects) | Declarative (data-driven) |
| Rendering | DOM elements | WebGL (much faster) |
| Performance | Slow with 500+ markers | Fast with 10,000+ points |
| Initialization | new google.maps.Map() |
new mapboxgl.Map() |
Quick Migration Checklist
✅ Install mapbox-gl package
✅ Get Mapbox access token
✅ Swap coordinate order (lat,lng → lng,lat)
✅ Replace Google Maps API with Mapbox equivalents
✅ Use Symbol layers for 100+ markers (not HTML markers)
✅ Add clustering for 500+ points
✅ Update geocoding to Mapbox Geocoding API
✅ Test all functionality
API Equivalents
Map Initialization
// Google Maps
const map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 37.7749, lng: -122.4194 },
zoom: 12
});
// Mapbox GL JS
mapboxgl.accessToken = 'pk.your_token';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-122.4194, 37.7749], // Note: [lng, lat]
zoom: 12
});
Individual Markers (< 50 points)
// Google Maps
const marker = new google.maps.Marker({
position: { lat: 37.7749, lng: -122.4194 },
map: map
});
// Mapbox (equivalent approach)
const marker = new mapboxgl.Marker().setLngLat([-122.4194, 37.7749]).addTo(map);
Many Markers (100+ points) - Performance Critical
// ❌ Google Maps: DOM-based (slow with 500+ markers)
locations.forEach((loc) => {
new google.maps.Marker({
position: { lat: loc.lat, lng: loc.lng },
map: map
});
});
// ✅ Mapbox: WebGL-based (fast with 10,000+ points)
map.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: locations.map((loc) => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [loc.lng, loc.lat] }
}))
}
});
map.addLayer({
id: 'points',
type: 'symbol',
source: 'points',
layout: {
'icon-image': 'marker-15'
}
});
Performance Note: Google Maps renders ALL markers as DOM elements (even with Data Layer). Mapbox uses WebGL for Symbol/Circle layers = 10-100x faster for large datasets.
Clustering (500+ points)
// Google Maps (requires MarkerClusterer library)
import MarkerClusterer from '@googlemaps/markerclustererplus';
const clusterer = new MarkerClusterer(map, markers);
// Mapbox (built-in)
map.addSource('points', {
type: 'geojson',
data: geojson,
cluster: true,
clusterRadius: 50
});
Info Windows / Popups
// Google Maps
const infowindow = new google.maps.InfoWindow({
content: '<h3>Title</h3>'
});
infowindow.open(map, marker);
// Mapbox
const popup = new mapboxgl.Popup().setHTML('<h3>Title</h3>').setLngLat([-122.4194, 37.7749]).addTo(map);
// Or attach to marker
marker.setPopup(popup);
Events
// Google Maps
marker.addListener('click', () => {
/* ... */
});
map.addListener('click', (e) => {
const lat = e.latLng.lat();
const lng = e.latLng.lng();
});
// Mapbox
marker.on('click', () => {
/* ... */
});
map.on('click', (e) => {
const [lng, lat] = [e.lngLat.lng, e.lngLat.lat];
});
Geocoding
// Google Maps
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ address: '1600 Amphitheatre Parkway' }, (results) => {
map.setCenter(results[0].geometry.location);
});
// Mapbox
fetch(
`https://api.mapbox.com/search/geocode/v6/forward?q=1600+Amphitheatre+Parkway&access_token=${mapboxgl.accessToken}`
)
.then((r) => r.json())
.then((data) => {
const [lng, lat] = data.features[0].geometry.coordinates;
map.setCenter([lng, lat]);
});
Directions
// Google Maps
const directionsService = new google.maps.DirectionsService();
directionsService.route(
{
origin: 'San Francisco',
destination: 'Los Angeles',
travelMode: 'DRIVING'
},
(result) => {
/* ... */
}
);
// Mapbox
fetch(
`https://api.mapbox.com/directions/v5/mapbox/driving/-122.4194,37.7749;-118.2437,34.0522?access_token=${mapboxgl.accessToken}`
)
.then((r) => r.json())
.then((data) => {
const route = data.routes[0].geometry;
// Display route on map
});
Polygons/Shapes
// Google Maps
const polygon = new google.maps.Polygon({
paths: coordinates,
map: map
});
// Mapbox
map.addSource('polygon', {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [coordinates] // Note: Array of arrays
}
}
});
map.addLayer({
id: 'polygon',
type: 'fill',
source: 'polygon',
paint: {
'fill-color': '#088',
'fill-opacity': 0.5
}
});
Coordinate Order - CRITICAL
Most common migration bug:
// ❌ Google Maps order (lat, lng)
{ lat: 37.7749, lng: -122.4194 }
// ✅ Mapbox order (lng, lat)
[-122.4194, 37.7749]
// Remember: Mapbox follows GeoJSON standard (longitude first)
Performance Advantages
Mapbox is significantly faster for:
- Large datasets: 500+ markers (Symbol layers vs DOM markers)
- Data visualization: Choropleth, heatmaps (WebGL rendering)
- Custom styling: Full control over every visual element
- Vector tiles: Efficient data loading and rendering
When Mapbox wins:
- Rendering 10,000+ points smoothly
- Custom map styles (not just pins on a map)
- Data-driven visualizations
- Performance-critical applications
When Google Maps might be better:
- Need Street View
- Heavy Google Workspace integration
- Places API is critical
- Team has deep Google Maps expertise
Styling Comparison
// Google Maps (limited styling)
const styledMapType = new google.maps.StyledMapType([{ elementType: 'geometry', stylers: [{ color: '#242f3e' }] }]);
// Mapbox (full control)
map.setStyle('mapbox://styles/mapbox/dark-v11');
// Or create custom styles in Mapbox Studio
Mapbox Styles:
streets-v12- Standard streetsoutdoors-v12- Hiking/outdoorlight-v11/dark-v11- Minimalsatellite-v9/satellite-streets-v12- Imagery- Custom styles via Mapbox Studio
Common Migration Patterns
Store Locator
Google Maps: Create marker for each store, add click listeners, show info windows Mapbox: Use Symbol layer + click events + popups (much faster for 100+ stores)
Route Display
Google Maps: DirectionsRenderer Mapbox: Fetch route from Directions API, add as Line layer
Heatmaps
Google Maps: HeatmapLayer (DOM-based) Mapbox: Heatmap layer (WebGL-based, much faster)
Token & Pricing
Google Maps:
- Requires API key
- Pay per map load + API calls
- Free tier: $200/month credit
Mapbox:
- Requires access token (pk.* for client-side)
- Pay per map load + API calls
- Free tier: 50,000 map loads/month
Token setup:
// Store in environment variables
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN;
Testing Migration
Checklist:
- ✅ Map displays at correct location
- ✅ All markers/pins visible
- ✅ Click events work
- ✅ Popups display correctly
- ✅ Geocoding returns results
- ✅ Directions routing works
- ✅ Performance improved (if using Symbol layers)
- ✅ Mobile works (touch events)
Migration Strategy
Phase 1: Setup
- Install Mapbox GL JS
- Get access token
- Create test page
Phase 2: Core Migration
- Initialize map
- Swap coordinate order
- Convert markers (use Symbol layers for 100+)
- Migrate popups/info windows
Phase 3: Features
- Geocoding
- Directions
- Custom styling
- Events
Phase 4: Optimization
- Add clustering (if 500+ points)
- Implement proper cleanup
- Test performance
- Mobile optimization
Quick Wins
Easy migrations (mostly drop-in replacements):
- Basic map initialization
- Individual markers (< 50)
- Popups
- Map controls
- Click events
Requires rethinking (but worth it):
- Large marker sets → Symbol layers (10-100x faster)
- Custom styling → Mapbox Studio
- Heatmaps → Heatmap layers (WebGL)
When NOT to Migrate
Consider staying with Google Maps if:
- Street View is critical
- Heavy Places API usage
- Team has deep Google Maps expertise
- Already heavily optimized
- Short-term project