{"name":"mapbox-web-integration-patterns","url":"https://skills.sh/mapbox/mapbox-agent-skills/mapbox-web-integration-patterns","install":"npx skills add mapbox/mapbox-agent-skills","sdk":"mapbox","key":"mapbox/mapbox-web-integration-patterns","description":"Official integration patterns for Mapbox GL JS across popular web frameworks (React, Vue, Svelte, Angular). Covers setup, lifecycle management, token handling, search integration, and common pitfalls. Based on Mapbox's create-web-app scaffolding tool.","hasContent":true,"content":"---\nname: mapbox-web-integration-patterns\ndescription: Official integration patterns for Mapbox GL JS across popular web frameworks (React, Vue, Svelte, Angular). Covers setup, lifecycle management, token handling, search integration, and common pitfalls. Based on Mapbox's create-web-app scaffolding tool.\n---\n\n# Mapbox Integration Patterns Skill\n\nThis skill provides official patterns for integrating Mapbox GL JS into web applications using React, Vue, Svelte, Angular, and vanilla JavaScript. These patterns are based on Mapbox's `create-web-app` scaffolding tool and represent production-ready best practices.\n\n## Version Requirements\n\n### Mapbox GL JS\n\n**Recommended:** v3.x (latest)\n\n- **Minimum:** v3.0.0\n- **Why v3.x:** Modern API, improved performance, active development\n- **v2.x:** Legacy; no longer actively developed (see migration notes below)\n\n**Installing via npm (recommended for production):**\n\n```bash\nnpm install mapbox-gl@^3.0.0    # Installs latest v3.x\n```\n\n**CDN (for prototyping only):**\n\n```html\n<!-- Replace VERSION with latest v3.x from https://docs.mapbox.com/mapbox-gl-js/ -->\n<script src=\"https://api.mapbox.com/mapbox-gl-js/vVERSION/mapbox-gl.js\"></script>\n<link href=\"https://api.mapbox.com/mapbox-gl-js/vVERSION/mapbox-gl.css\" rel=\"stylesheet\" />\n```\n\n### Framework Requirements\n\n**React:** GL JS works with React 16.8+ (requires hooks). `create-web-app` scaffolds with React 19.x.\n**Vue:** GL JS works with Vue 2.x+ (Vue 3 Composition API recommended).\n**Svelte:** GL JS works with any Svelte version. `create-web-app` scaffolds with Svelte 5.x.\n**Angular:** GL JS works with Angular 2+. `create-web-app` scaffolds with Angular 19.x.\n**Next.js:** Minimum 13.x (App Router), Pages Router 12.x+.\n\n### Mapbox Search JS\n\n```bash\nnpm install @mapbox/search-js-react@^1.0.0      # React\nnpm install @mapbox/search-js-web@^1.0.0        # Other frameworks\n```\n\n### Version Migration Notes (v2.x to v3.x)\n\n- WebGL 2 now required\n- `optimizeForTerrain` option removed\n- Improved TypeScript types, better tree-shaking support\n- No breaking changes to core initialization patterns\n\n**Token patterns (work in v2.x and v3.x):**\n\n```javascript\nconst token = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN; // Use env vars in production\n\n// Global token (works since v1.x)\nmapboxgl.accessToken = token;\nconst map = new mapboxgl.Map({ container: '...' });\n\n// Per-map token (preferred for multi-map setups)\nconst map = new mapboxgl.Map({\n  accessToken: token,\n  container: '...'\n});\n```\n\n## Core Principles\n\n**Every Mapbox GL JS integration must:**\n\n1. Initialize the map in the correct lifecycle hook\n2. Store map instance in component state (not recreate on every render)\n3. **Always call `map.remove()` on cleanup** to prevent memory leaks\n4. Handle token management securely (environment variables)\n5. Import CSS: `import 'mapbox-gl/dist/mapbox-gl.css'`\n\n## React Integration (Primary Pattern)\n\n**Pattern: useRef + useEffect with cleanup**\n\n> **Note:** These examples use **Vite** (the bundler used in `create-web-app`). If using Create React App, replace `import.meta.env.VITE_MAPBOX_ACCESS_TOKEN` with `process.env.REACT_APP_MAPBOX_TOKEN`. See [Token Management Patterns](references/token-management.md) for other bundlers.\n\n```jsx\nimport { useRef, useEffect } from 'react';\nimport mapboxgl from 'mapbox-gl';\nimport 'mapbox-gl/dist/mapbox-gl.css';\n\nfunction MapComponent() {\n  const mapRef = useRef(null); // Store map instance\n  const mapContainerRef = useRef(null); // Store DOM reference\n\n  useEffect(() => {\n    mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;\n\n    mapRef.current = new mapboxgl.Map({\n      container: mapContainerRef.current,\n      center: [-71.05953, 42.3629],\n      zoom: 13\n    });\n\n    // CRITICAL: Cleanup to prevent memory leaks\n    return () => {\n      mapRef.current.remove();\n    };\n  }, []); // Empty dependency array = run once on mount\n\n  return <div ref={mapContainerRef} style={{ height: '100vh' }} />;\n}\n```\n\n**Key points:**\n\n- Use `useRef` for both map instance and container\n- Initialize in `useEffect` with empty deps `[]`\n- **Always return cleanup function** that calls `map.remove()`\n- Never initialize map in render (causes infinite loops)\n\n### React + Search JS\n\n```jsx\nimport { useRef, useEffect, useState } from 'react';\nimport mapboxgl from 'mapbox-gl';\nimport { SearchBox } from '@mapbox/search-js-react';\nimport 'mapbox-gl/dist/mapbox-gl.css';\n\nconst accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;\nconst center = [-71.05953, 42.3629];\n\nfunction MapWithSearch() {\n  const mapRef = useRef(null);\n  const mapContainerRef = useRef(null);\n  const [inputValue, setInputValue] = useState('');\n\n  useEffect(() => {\n    mapboxgl.accessToken = accessToken;\n\n    mapRef.current = new mapboxgl.Map({\n      container: mapContainerRef.current,\n      center: center,\n      zoom: 13\n    });\n\n    return () => {\n      mapRef.current.remove();\n    };\n  }, []);\n\n  return (\n    <>\n      <div\n        style={{\n          margin: '10px 10px 0 0',\n          width: 300,\n          right: 0,\n          top: 0,\n          position: 'absolute',\n          zIndex: 10\n        }}\n      >\n        <SearchBox\n          accessToken={accessToken}\n          map={mapRef.current}\n          mapboxgl={mapboxgl}\n          value={inputValue}\n          proximity={center}\n          onChange={(d) => setInputValue(d)}\n          marker\n        />\n      </div>\n      <div ref={mapContainerRef} style={{ height: '100vh' }} />\n    </>\n  );\n}\n```\n\n## Search JS Integration Summary\n\n**Install:**\n\n```bash\nnpm install @mapbox/search-js-react      # React\nnpm install @mapbox/search-js-web        # Vanilla/Vue/Svelte\n```\n\nBoth packages include `@mapbox/search-js-core` as a dependency. Only install `-core` directly if building a custom search UI.\n\n**Key configuration options:**\n\n- `accessToken`: Your Mapbox public token\n- `map`: Map instance (must be initialized first)\n- `mapboxgl`: The mapboxgl library reference\n- `proximity`: `[lng, lat]` to bias results geographically\n- `marker`: Boolean to show/hide result marker\n- `placeholder`: Search box placeholder text\n\n### Positioning Search Box\n\n**Absolute positioning (overlay):**\n\n```jsx\n<div\n  style={{\n    position: 'absolute',\n    top: 10,\n    right: 10,\n    zIndex: 10,\n    width: 300\n  }}\n>\n  <SearchBox {...props} />\n</div>\n```\n\n**Common positions:**\n\n- Top-right: `top: 10px, right: 10px`\n- Top-left: `top: 10px, left: 10px`\n- Bottom-left: `bottom: 10px, left: 10px`\n\n## Common Mistakes (Critical)\n\n### Mistake 1: Forgetting to call map.remove()\n\n```javascript\n// BAD - Memory leak!\nuseEffect(() => {\n  const map = new mapboxgl.Map({ ... })\n  // No cleanup function\n}, [])\n\n// GOOD - Proper cleanup\nuseEffect(() => {\n  const map = new mapboxgl.Map({ ... })\n  return () => map.remove()  // Cleanup\n}, [])\n```\n\n**Why:** Every Map instance creates WebGL contexts, event listeners, and DOM nodes. Without cleanup, these accumulate and cause memory leaks.\n\n### Mistake 2: Initializing map in render\n\n```javascript\n// BAD - Infinite loop in React!\nfunction MapComponent() {\n  const map = new mapboxgl.Map({ ... })  // Runs on every render\n  return <div />\n}\n\n// GOOD - Initialize in effect\nfunction MapComponent() {\n  useEffect(() => {\n    const map = new mapboxgl.Map({ ... })\n  }, [])\n  return <div />\n}\n```\n\n**Why:** React components re-render frequently. Creating a new map on every render causes infinite loops and crashes.\n\n### Mistake 3: Not storing map instance properly\n\n```javascript\n// BAD - map variable lost between renders\nfunction MapComponent() {\n  useEffect(() => {\n    let map = new mapboxgl.Map({ ... })\n    // map variable is not accessible later\n  }, [])\n}\n\n// GOOD - Store in useRef\nfunction MapComponent() {\n  const mapRef = useRef()\n  useEffect(() => {\n    mapRef.current = new mapboxgl.Map({ ... })\n    // mapRef.current accessible throughout component\n  }, [])\n}\n```\n\n**Why:** You need to access the map instance for operations like adding layers, markers, or calling `remove()`.\n\n### Mistake 4: Storing map instance in Vue's data() (Vue-specific)\n\n```javascript\n// BAD - Vue's reactivity wraps data() objects in a Proxy, breaking mapbox-gl internals!\nexport default {\n  data() {\n    return {\n      map: null  // Will be wrapped in a Proxy\n    }\n  },\n  mounted() {\n    this.map = new mapboxgl.Map({ ... })  // Proxy breaks GL internals\n  }\n}\n\n// GOOD - Assign map as a plain instance property, not in data()\nexport default {\n  mounted() {\n    this.map = new mapboxgl.Map({\n      container: this.$refs.mapContainer,\n      center: [-71.05953, 42.3629],\n      zoom: 13\n    })\n  },\n  unmounted() {\n    this.map?.remove()\n  }\n}\n```\n\n**Why:** In Vue (especially Vue 3), `data()` properties are wrapped in a `Proxy` for reactivity. Mapbox GL JS internally checks object identity and uses properties that don't survive proxy wrapping. Storing the map in `data()` causes subtle, hard-to-debug failures. Instead, assign the map instance directly as `this.map` in `mounted()` — properties assigned outside `data()` are not made reactive.\n\n## Reference Files\n\nLoad these for framework-specific patterns and additional details:\n\n- `references/vue.md` — Vue Integration (mounted/unmounted lifecycle)\n- `references/svelte.md` — Svelte Integration (onMount/onDestroy)\n- `references/angular.md` — Angular Integration with SSR handling\n- `references/vanilla.md` — Vanilla JS (Vite) + Vanilla JS (CDN)\n- `references/web-components.md` — Web Components (basic + reactive + usage in React/Vue/Svelte)\n- `references/nextjs.md` — Next.js App Router + Pages Router\n- `references/common-mistakes.md` — Common Mistakes 4-7 + Testing Patterns\n- `references/token-management.md` — Token Management per bundler + Style Configuration\n\n## When to Use This Skill\n\nInvoke this skill when:\n\n- Setting up Mapbox GL JS in a new project\n- Integrating Mapbox into a specific framework (React, Vue, Svelte, Angular, Next.js)\n- Building framework-agnostic Web Components\n- Creating reusable map components for component libraries\n- Debugging map initialization issues\n- Adding Mapbox Search functionality\n- Implementing proper cleanup and lifecycle management\n- Converting between frameworks (e.g., React to Vue)\n- Reviewing code for Mapbox integration best practices\n\n## Related Skills\n\n- **mapbox-cartography**: Map design principles and styling\n- **mapbox-token-security**: Token management and security\n- **mapbox-style-patterns**: Common map style patterns\n\n## Resources\n\n- [Mapbox GL JS Documentation](https://docs.mapbox.com/mapbox-gl-js/)\n- [Mapbox Search JS Documentation](https://docs.mapbox.com/mapbox-search-js/)\n- [create-web-app GitHub](https://github.com/mapbox/create-web-app)\n","contentSource":"skills.sh/api/download/mapbox/mapbox-agent-skills/mapbox-web-integration-patterns","contentFetchedAt":"2026-07-27T08:59:31.118Z"}