
Mapbox Web Integration Patterns
Embed Mapbox GL JS in React, Vue, Svelte, Angular, or Next.js without remount leaks or racing map load.
Overview
mapbox-web-integration-patterns is an agent skill for the Build phase that shows how to integrate Mapbox GL JS in React, Vue, Svelte, Angular, and Next.js with correct lifecycle and load handling.
Install
npx skills add https://github.com/mapbox/mapbox-agent-skills --skill mapbox-web-integration-patternsWhat is this skill?
- Critical rules: initialize once, `map.remove()` on unmount, empty-deps mount pattern
- Separate effects for create vs `setCenter`/layer updates to avoid re-instantiating Map
- All sources and layers added only after map `load` event
- Framework quick refs for React, Vue, Svelte, Angular, and Next.js
- Ref-guard pattern to skip double init when strict mode or remounts fire
- Documents integration patterns for five frameworks: React, Vue, Svelte, Angular, and Next.js
Adoption & trust: 1.2k installs on skills.sh; 64 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your Mapbox map flickers, leaks memory, or shows blank tiles because the agent keeps creating new `mapboxgl.Map` instances on every render.
Who is it for?
Indie devs adding interactive maps to React or Next.js SaaS surfaces who want copy-paste-safe lifecycle patterns.
Skip if: Native iOS/Android Mapbox SDK projects or backend-only geospatial ETL with no GL JS UI.
When should I use this skill?
You are adding or refactoring a Mapbox GL JS map in a web framework and need correct init, load, and teardown.
What do I get? / Deliverables
You get framework-specific snippets that mount once, clean up on unmount, and update center or layers only after the map `load` event.
- Framework-specific map component scaffold with cleanup
- Load-gated source/layer update pattern
Recommended Skills
Journey fit
Map UI wiring is frontend build work once you have tokens, routes, and data layers to show. The skill encodes framework lifecycle hooks and GL JS load events—browser integration, not server cartography ops.
How it compares
Framework integration skill—not the Mapbox CLI, tile server, or a hosted maps MCP.
Common Questions / FAQ
Who is mapbox-web-integration-patterns for?
Frontend-focused solo builders integrating Mapbox GL JS in modern SPAs or Next.js apps.
When should I use mapbox-web-integration-patterns?
During Build frontend work when scaffolding a map component, fixing strict-mode double mount, or aligning Vue/Svelte/Angular hooks with GL JS load rules.
Is mapbox-web-integration-patterns safe to install?
Check Security Audits on this Prism page; map skills imply browser and API token usage—never commit production Mapbox secrets to the repo.
SKILL.md
READMESKILL.md - Mapbox Web Integration Patterns
# Mapbox Framework Integration Guide Quick reference for integrating Mapbox GL JS with React, Vue, Svelte, Angular, and Next.js. ## Critical Integration Rules ### 1. Map Lifecycle Management **Must properly initialize and cleanup in all frameworks:** ```javascript // ✅ Initialize once, cleanup on unmount useEffect(() => { const map = new mapboxgl.Map({...}); return () => map.remove(); // Critical: prevents memory leaks }, []); // Empty deps = mount once ``` ### 2. Never Re-initialize Map **Problem:** Creating new map instances causes memory leaks **Solution:** Initialize once with empty dependency array ```javascript // ❌ Bad: Re-creates map on every state change useEffect(() => { const map = new mapboxgl.Map({...}); }, [someState]); // Re-runs on state change! // ✅ Good: Create once, update separately useEffect(() => { const map = new mapboxgl.Map({...}); return () => map.remove(); }, []); // Only runs once useEffect(() => { if (map) map.setCenter(center); }, [center]); // Update separately ``` ### 3. Wait for Map Load **All map operations must wait for 'load' event:** ```javascript map.on('load', () => { // ✅ Now safe to add sources/layers map.addSource('data', {...}); map.addLayer({...}); }); ``` ## Framework-Specific Patterns ### React ```javascript import { useEffect, useRef } from 'react'; function MapComponent() { const mapContainer = useRef(null); const map = useRef(null); useEffect(() => { if (map.current) return; // Initialize only once map.current = new mapboxgl.Map({ container: mapContainer.current, style: 'mapbox://styles/mapbox/streets-v12', center: [-122.4, 37.8], zoom: 12 }); return () => map.current.remove(); }, []); // Update map when props change useEffect(() => { if (!map.current) return; map.current.setCenter([lng, lat]); }, [lng, lat]); return <div ref={mapContainer} style={{ width: '100%', height: '400px' }} />; } ``` ### Next.js (with SSR) ```javascript 'use client'; // Mark as client component import dynamic from 'next/dynamic'; // Option 1: Dynamic import (recommended) const Map = dynamic(() => import('./Map'), { ssr: false }); // Option 2: Check for window useEffect(() => { if (typeof window === 'undefined') return; const mapboxgl = require('mapbox-gl'); // Initialize map... }, []); ``` ### Vue 3 (Composition API) ```javascript import { onMounted, onUnmounted, ref } from 'vue'; export default { setup() { const mapContainer = ref(null); let map = null; onMounted(() => { map = new mapboxgl.Map({ container: mapContainer.value, style: 'mapbox://styles/mapbox/streets-v12' }); }); onUnmounted(() => { map?.remove(); }); return { mapContainer }; } }; ``` ### Svelte ```javascript <script> import { onMount, onDestroy } from 'svelte'; import mapboxgl from 'mapbox-gl'; let mapContainer; let map; onMount(() => { map = new mapboxgl.Map({ container: mapContainer, style: 'mapbox://styles/mapbox/streets-v12' }); }); onDestroy(() => { map?.remove(); }); </script> <div bind:this={mapContainer}></div> ``` ### Angular ```typescript import { Component, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core'; import mapboxgl from 'mapbox-gl'; @Component({ selector: 'app-map', template: '<div #mapContainer></div>' }) export class MapComponent implements OnInit, OnDestroy { @ViewChild('mapContainer', { static: true }) mapContainer!: ElementRef; private map!: mapboxgl.Map; ngOnInit() { this.map = new mapboxgl.Map({ container: this.mapContainer.nativeElement, style: 'mapbox://styles/mapbox/streets-v12' }); } ngOnDestroy() { this.map?.remove(); } } ``` ## State Management Patterns ### Updating Map from State ```javascript // ✅ Separate effects for different updates useEffect(() => { if (!map.current) return; map.current.setCenter(center);