mapbox-integration-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Mapbox Integration Patterns Skill

Mapbox集成模式Skill

This skill provides official patterns for integrating Mapbox GL JS into web applications across different frameworks. These patterns are based on Mapbox's
create-web-app
scaffolding tool and represent production-ready best practices.
本Skill提供了在不同框架中为Web应用集成Mapbox GL JS的官方模式。这些模式基于Mapbox的
create-web-app
脚手架工具,代表了生产环境就绪的最佳实践。

Version Requirements

版本要求

Mapbox GL JS

Mapbox GL JS

Recommended: v3.x (latest)
  • Minimum: v3.0.0
  • Why v3.x: Modern API, improved performance, active development
  • v2.x: Still supported but deprecated patterns (see migration notes below)
Installing via npm (recommended for production):
bash
npm install mapbox-gl@^3.0.0    # Installs latest v3.x
CDN (for prototyping only):
html
<!-- Replace VERSION with latest v3.x from https://docs.mapbox.com/mapbox-gl-js/ -->
<script src="https://api.mapbox.com/mapbox-gl-js/vVERSION/mapbox-gl.js"></script>
<link
  href="https://api.mapbox.com/mapbox-gl-js/vVERSION/mapbox-gl.css"
  rel="stylesheet"
/>
⚠️ Production apps should use npm, not CDN - ensures consistent versions and offline builds.
推荐版本: v3.x(最新版)
  • 最低版本: v3.0.0
  • 为什么选择v3.x: 现代API、性能提升、持续开发维护
  • v2.x: 仍受支持但模式已过时(详见下方迁移说明)
通过npm安装(生产环境推荐):
bash
npm install mapbox-gl@^3.0.0    # Installs latest v3.x
CDN方式(仅用于原型开发):
html
<!-- Replace VERSION with latest v3.x from https://docs.mapbox.com/mapbox-gl-js/ -->
<script src="https://api.mapbox.com/mapbox-gl-js/vVERSION/mapbox-gl.js"></script>
<link
  href="https://api.mapbox.com/mapbox-gl-js/vVERSION/mapbox-gl.css"
  rel="stylesheet"
/>
⚠️ 生产环境应用应使用npm而非CDN - 确保版本一致并支持离线构建。

Framework Requirements

框架要求

React:
  • Minimum: 19+ (current implementation in create-web-app)
  • Recommended: Latest 19.x
Vue:
  • Minimum: 3.x (Composition API recommended)
  • Vue 2.x: Use Options API pattern (mounted/unmounted hooks)
Svelte:
  • Minimum: 5+ (current implementation in create-web-app)
  • Recommended: Latest 5.x
Angular:
  • Minimum: 19+ (current implementation in create-web-app)
  • Recommended: Latest 19.x
Next.js:
  • Minimum: 13.x (App Router)
  • Pages Router: 12.x+
React:
  • 最低版本:19+(create-web-app中的当前实现版本)
  • 推荐版本:最新19.x
Vue:
  • 最低版本:3.x(推荐使用组合式API)
  • Vue 2.x:使用选项式API模式(mounted/unmounted钩子)
Svelte:
  • 最低版本:5+(create-web-app中的当前实现版本)
  • 推荐版本:最新5.x
Angular:
  • 最低版本:19+(create-web-app中的当前实现版本)
  • 推荐版本:最新19.x
Next.js:
  • 最低版本:13.x(App Router)
  • Pages Router:12.x+

Mapbox Search JS

Mapbox Search JS

Required for search integration:
bash
npm install @mapbox/search-js-react@^1.0.0      # React
npm install @mapbox/search-js-web@^1.0.0        # Other frameworks
搜索集成所需依赖:
bash
npm install @mapbox/search-js-react@^1.0.0      # React
npm install @mapbox/search-js-web@^1.0.0        # 其他框架

Version Migration Notes

版本迁移说明

Migrating from v2.x to v3.x:
  • accessToken
    can now be passed to Map constructor (preferred)
  • Improved TypeScript types
  • Better tree-shaking support
  • No breaking changes to core initialization patterns
Example:
javascript
const token = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN; // Use env vars in production

// v2.x pattern (still works in v3.x)
mapboxgl.accessToken = token;
const map = new mapboxgl.Map({ container: '...' });

// v3.x pattern (preferred)
const map = new mapboxgl.Map({
  accessToken: token,
  container: '...'
});
从v2.x迁移到v3.x:
  • accessToken
    现在可传递给Map构造函数(推荐方式)
  • 改进的TypeScript类型定义
  • 更好的树摇支持
  • 核心初始化模式无破坏性变更
示例:
javascript
const token = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN; // Use env vars in production

// v2.x pattern (still works in v3.x)
mapboxgl.accessToken = token;
const map = new mapboxgl.Map({ container: '...' });

// v3.x pattern (preferred)
const map = new mapboxgl.Map({
  accessToken: token,
  container: '...'
});

Core Principles

核心原则

Every Mapbox GL JS integration must:
  1. Initialize the map in the correct lifecycle hook
  2. Store map instance in component state (not recreate on every render)
  3. Always call
    map.remove()
    on cleanup
    to prevent memory leaks
  4. Handle token management securely (environment variables)
  5. Import CSS:
    import 'mapbox-gl/dist/mapbox-gl.css'
所有Mapbox GL JS集成必须满足:
  1. 在正确的生命周期钩子中初始化地图
  2. 将地图实例存储在组件状态中(不要在每次渲染时重新创建)
  3. 清理时务必调用
    map.remove()
    以防止内存泄漏
  4. 安全处理令牌管理(使用环境变量)
  5. 导入CSS:
    import 'mapbox-gl/dist/mapbox-gl.css'

Framework-Specific Patterns

框架特定模式

React Integration

React集成

Pattern: useRef + useEffect with cleanup
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 the Token Management Patterns section for other bundlers.
jsx
import { useRef, useEffect } from 'react';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

function MapComponent() {
  const mapRef = useRef(null); // Store map instance
  const mapContainerRef = useRef(null); // Store DOM reference

  useEffect(() => {
    mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;

    mapRef.current = new mapboxgl.Map({
      container: mapContainerRef.current,
      center: [-71.05953, 42.3629],
      zoom: 13
    });

    // CRITICAL: Cleanup to prevent memory leaks
    return () => {
      mapRef.current.remove();
    };
  }, []); // Empty dependency array = run once on mount

  return <div ref={mapContainerRef} style={{ height: '100vh' }} />;
}
Key points:
  • Use
    useRef
    for both map instance and container
  • Initialize in
    useEffect
    with empty deps
    []
  • Always return cleanup function that calls
    map.remove()
  • Never initialize map in render (causes infinite loops)
React + Search JS:
jsx
import { useRef, useEffect, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import { SearchBox } from '@mapbox/search-js-react';
import 'mapbox-gl/dist/mapbox-gl.css';

const accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;
const center = [-71.05953, 42.3629];

function MapWithSearch() {
  const mapRef = useRef(null);
  const mapContainerRef = useRef(null);
  const [inputValue, setInputValue] = useState('');

  useEffect(() => {
    mapboxgl.accessToken = accessToken;

    mapRef.current = new mapboxgl.Map({
      container: mapContainerRef.current,
      center: center,
      zoom: 13
    });

    return () => {
      mapRef.current.remove();
    };
  }, []);

  return (
    <>
      <div
        style={{
          margin: '10px 10px 0 0',
          width: 300,
          right: 0,
          top: 0,
          position: 'absolute',
          zIndex: 10
        }}
      >
        <SearchBox
          accessToken={accessToken}
          map={mapRef.current}
          mapboxgl={mapboxgl}
          value={inputValue}
          proximity={center}
          onChange={(d) => setInputValue(d)}
          marker
        />
      </div>
      <div ref={mapContainerRef} style={{ height: '100vh' }} />
    </>
  );
}

模式:useRef + 带清理的useEffect
注意: 这些示例使用Vite
create-web-app
所使用的打包工具)。如果使用Create React App,请将
import.meta.env.VITE_MAPBOX_ACCESS_TOKEN
替换为
process.env.REACT_APP_MAPBOX_TOKEN
。其他打包工具的令牌管理方式请参见令牌管理模式章节。
jsx
import { useRef, useEffect } from 'react';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

function MapComponent() {
  const mapRef = useRef(null); // Store map instance
  const mapContainerRef = useRef(null); // Store DOM reference

  useEffect(() => {
    mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;

    mapRef.current = new mapboxgl.Map({
      container: mapContainerRef.current,
      center: [-71.05953, 42.3629],
      zoom: 13
    });

    // CRITICAL: Cleanup to prevent memory leaks
    return () => {
      mapRef.current.remove();
    };
  }, []); // Empty dependency array = run once on mount

  return <div ref={mapContainerRef} style={{ height: '100vh' }} />;
}
关键点:
  • 对地图实例和容器都使用
    useRef
  • useEffect
    中初始化,依赖项数组为空
    []
  • 务必返回清理函数 调用
    map.remove()
  • 绝不要在渲染函数中初始化地图(会导致无限循环)
React + Search JS:
jsx
import { useRef, useEffect, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import { SearchBox } from '@mapbox/search-js-react';
import 'mapbox-gl/dist/mapbox-gl.css';

const accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;
const center = [-71.05953, 42.3629];

function MapWithSearch() {
  const mapRef = useRef(null);
  const mapContainerRef = useRef(null);
  const [inputValue, setInputValue] = useState('');

  useEffect(() => {
    mapboxgl.accessToken = accessToken;

    mapRef.current = new mapboxgl.Map({
      container: mapContainerRef.current,
      center: center,
      zoom: 13
    });

    return () => {
      mapRef.current.remove();
    };
  }, []);

  return (
    <>
      <div
        style={{
          margin: '10px 10px 0 0',
          width: 300,
          right: 0,
          top: 0,
          position: 'absolute',
          zIndex: 10
        }}
      >
        <SearchBox
          accessToken={accessToken}
          map={mapRef.current}
          mapboxgl={mapboxgl}
          value={inputValue}
          proximity={center}
          onChange={(d) => setInputValue(d)}
          marker
        />
      </div>
      <div ref={mapContainerRef} style={{ height: '100vh' }} />
    </>
  );
}

Vue Integration

Vue集成

Pattern: mounted + unmounted lifecycle hooks
vue
<template>
  <div ref="mapContainer" class="map-container"></div>
</template>

<script>
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;

export default {
  mounted() {
    const map = new mapboxgl.Map({
      container: this.$refs.mapContainer,
      style: 'mapbox://styles/mapbox/standard',
      center: [-71.05953, 42.3629],
      zoom: 13
    });

    // Assign map instance to component property
    this.map = map;
  },

  // CRITICAL: Clean up when component is unmounted
  unmounted() {
    this.map.remove();
    this.map = null;
  }
};
</script>

<style>
.map-container {
  width: 100%;
  height: 100%;
}
</style>
Key points:
  • Initialize in
    mounted()
    hook
  • Access container via
    this.$refs.mapContainer
  • Store map as
    this.map
  • Always implement
    unmounted()
    hook
    to call
    map.remove()

模式:mounted + unmounted生命周期钩子
vue
<template>
  <div ref="mapContainer" class="map-container"></div>
</template>

<script>
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;

export default {
  mounted() {
    const map = new mapboxgl.Map({
      container: this.$refs.mapContainer,
      style: 'mapbox://styles/mapbox/standard',
      center: [-71.05953, 42.3629],
      zoom: 13
    });

    // Assign map instance to component property
    this.map = map;
  },

  // CRITICAL: Clean up when component is unmounted
  unmounted() {
    this.map.remove();
    this.map = null;
  }
};
</script>

<style>
.map-container {
  width: 100%;
  height: 100%;
}
</style>
关键点:
  • mounted()
    钩子中初始化
  • 通过
    this.$refs.mapContainer
    访问容器
  • 将地图实例存储为
    this.map
  • 务必实现
    unmounted()
    钩子
    调用
    map.remove()

Svelte Integration

Svelte集成

Pattern: onMount + onDestroy
svelte
<script>
  import { Map } from 'mapbox-gl'
  import 'mapbox-gl/dist/mapbox-gl.css'
  import { onMount, onDestroy } from 'svelte'

  let map
  let mapContainer

  onMount(() => {
    map = new Map({
      container: mapContainer,
      accessToken: import.meta.env.VITE_MAPBOX_ACCESS_TOKEN,
      center: [-71.05953, 42.36290],
      zoom: 13
    })
  })

  // CRITICAL: Clean up on component destroy
  onDestroy(() => {
    map.remove()
  })
</script>

<div class="map" bind:this={mapContainer}></div>

<style>
  .map {
    position: absolute;
    width: 100%;
    height: 100%;
  }
</style>
Key points:
  • Use
    onMount
    for initialization
  • Bind container with
    bind:this={mapContainer}
  • Always implement
    onDestroy
    to call
    map.remove()
  • Can pass
    accessToken
    directly to Map constructor in Svelte

模式:onMount + onDestroy
svelte
<script>
  import { Map } from 'mapbox-gl'
  import 'mapbox-gl/dist/mapbox-gl.css'
  import { onMount, onDestroy } from 'svelte'

  let map
  let mapContainer

  onMount(() => {
    map = new Map({
      container: mapContainer,
      accessToken: import.meta.env.VITE_MAPBOX_ACCESS_TOKEN,
      center: [-71.05953, 42.36290],
      zoom: 13
    })
  })

  // CRITICAL: Clean up on component destroy
  onDestroy(() => {
    map.remove()
  })
</script>

<div class="map" bind:this={mapContainer}></div>

<style>
  .map {
    position: absolute;
    width: 100%;
    height: 100%;
  }
</style>
关键点:
  • 使用
    onMount
    进行初始化
  • 通过
    bind:this={mapContainer}
    绑定容器
  • 务必实现
    onDestroy
    调用
    map.remove()
  • 在Svelte中可直接将
    accessToken
    传递给Map构造函数

Angular Integration

Angular集成

Pattern: ngOnInit + ngOnDestroy with SSR handling
typescript
import {
  Component,
  ElementRef,
  OnDestroy,
  OnInit,
  ViewChild,
  inject
} from '@angular/core';
import { isPlatformBrowser, CommonModule } from '@angular/common';
import { PLATFORM_ID } from '@angular/core';
import { environment } from '../../environments/environment';

@Component({
  selector: 'app-map',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.scss']
})
export class MapComponent implements OnInit, OnDestroy {
  @ViewChild('mapContainer', { static: false })
  mapContainer!: ElementRef<HTMLDivElement>;

  private map: any;
  private readonly platformId = inject(PLATFORM_ID);

  async ngOnInit(): Promise<void> {
    // IMPORTANT: Check if running in browser (not SSR)
    if (!isPlatformBrowser(this.platformId)) {
      return;
    }

    try {
      await this.initializeMap();
    } catch (error) {
      console.error('Failed to initialize map:', error);
    }
  }

  private async initializeMap(): Promise<void> {
    // Dynamically import to avoid SSR issues
    const mapboxgl = (await import('mapbox-gl')).default;

    this.map = new mapboxgl.Map({
      accessToken: environment.mapboxAccessToken,
      container: this.mapContainer.nativeElement,
      center: [-71.05953, 42.3629],
      zoom: 13
    });

    // Handle map errors
    this.map.on('error', (e: any) => console.error('Map error:', e.error));
  }

  // CRITICAL: Clean up on component destroy
  ngOnDestroy(): void {
    if (this.map) {
      this.map.remove();
    }
  }
}
Template (map.component.html):
html
<div #mapContainer style="height: 100vh; width: 100%"></div>
Key points:
  • Use
    @ViewChild
    to reference map container
  • Check
    isPlatformBrowser
    before initializing
    (SSR support)
  • Dynamically import
    mapbox-gl
    to avoid SSR issues
  • Initialize in
    ngOnInit()
    lifecycle hook
  • Always implement
    ngOnDestroy()
    to call
    map.remove()
  • Handle errors with
    map.on('error', ...)

模式:ngOnInit + ngOnDestroy(支持SSR)
typescript
import {
  Component,
  ElementRef,
  OnDestroy,
  OnInit,
  ViewChild,
  inject
} from '@angular/core';
import { isPlatformBrowser, CommonModule } from '@angular/common';
import { PLATFORM_ID } from '@angular/core';
import { environment } from '../../environments/environment';

@Component({
  selector: 'app-map',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.scss']
})
export class MapComponent implements OnInit, OnDestroy {
  @ViewChild('mapContainer', { static: false })
  mapContainer!: ElementRef<HTMLDivElement>;

  private map: any;
  private readonly platformId = inject(PLATFORM_ID);

  async ngOnInit(): Promise<void> {
    // IMPORTANT: Check if running in browser (not SSR)
    if (!isPlatformBrowser(this.platformId)) {
      return;
    }

    try {
      await this.initializeMap();
    } catch (error) {
      console.error('Failed to initialize map:', error);
    }
  }

  private async initializeMap(): Promise<void> {
    // Dynamically import to avoid SSR issues
    const mapboxgl = (await import('mapbox-gl')).default;

    this.map = new mapboxgl.Map({
      accessToken: environment.mapboxAccessToken,
      container: this.mapContainer.nativeElement,
      center: [-71.05953, 42.3629],
      zoom: 13
    });

    // Handle map errors
    this.map.on('error', (e: any) => console.error('Map error:', e.error));
  }

  // CRITICAL: Clean up on component destroy
  ngOnDestroy(): void {
    if (this.map) {
      this.map.remove();
    }
  }
}
模板(map.component.html):
html
<div #mapContainer style="height: 100vh; width: 100%"></div>
关键点:
  • 使用
    @ViewChild
    引用地图容器
  • 初始化前检查
    isPlatformBrowser
    (支持SSR)
  • 动态导入
    mapbox-gl
    避免SSR问题
  • ngOnInit()
    生命周期钩子中初始化
  • 务必实现
    ngOnDestroy()
    调用
    map.remove()
  • 通过
    map.on('error', ...)
    处理地图错误

Vanilla JavaScript (with Vite)

原生JavaScript(搭配Vite)

Pattern: Module imports with initialization function
javascript
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import './main.css';

// Set access token
mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;

let map;

/**
 * Initialize the map
 */
function initMap() {
  map = new mapboxgl.Map({
    container: 'map-container',
    center: [-71.05953, 42.3629],
    zoom: 13
  });

  map.on('load', () => {
    console.log('Map is loaded');
  });
}

// Initialize when script runs
initMap();
HTML:
html
<div id="map-container" style="height: 100vh;"></div>
Key points:
  • Store map in module-scoped variable
  • Initialize immediately or on DOMContentLoaded
  • Listen for 'load' event for post-initialization actions

模式:模块导入+初始化函数
javascript
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import './main.css';

// Set access token
mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;

let map;

/**
 * Initialize the map
 */
function initMap() {
  map = new mapboxgl.Map({
    container: 'map-container',
    center: [-71.05953, 42.3629],
    zoom: 13
  });

  map.on('load', () => {
    console.log('Map is loaded');
  });
}

// Initialize when script runs
initMap();
HTML:
html
<div id="map-container" style="height: 100vh;"></div>
关键点:
  • 将地图实例存储在模块作用域变量中
  • 立即初始化或在DOMContentLoaded事件触发时初始化
  • 监听'load'事件以执行初始化后的操作

Vanilla JavaScript (No Bundler - CDN)

原生JavaScript(无打包工具 - CDN)

Pattern: Script tag with inline initialization
⚠️ Note: This pattern is for prototyping only. Production apps should use npm/bundler for version control and offline builds.
html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mapbox GL JS - No Bundler</title>

    <!-- Mapbox GL JS CSS -->
    <!-- Replace 3.x.x with latest version from https://docs.mapbox.com/mapbox-gl-js/ -->
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v3.x.x/mapbox-gl.css"
      rel="stylesheet"
    />

    <style>
      body {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        margin: 0;
        padding: 0;
      }
      #map-container {
        height: 100%;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div id="map-container"></div>

    <!-- Mapbox GL JS -->
    <!-- Replace 3.x.x with latest version from https://docs.mapbox.com/mapbox-gl-js/ -->
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.x.x/mapbox-gl.js"></script>

    <script>
      // Set access token
      mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN_HERE';

      let map;

      function initMap() {
        map = new mapboxgl.Map({
          container: 'map-container',
          center: [-71.05953, 42.3629],
          zoom: 13
        });

        map.on('load', () => {
          console.log('Map is loaded');
        });
      }

      // Initialize when page loads
      initMap();
    </script>
  </body>
</html>
Key points:
  • ⚠️ Prototyping only - not recommended for production
  • Replace
    3.x.x
    with specific version (e.g.,
    3.7.0
    ) from Mapbox docs
  • Don't use
    /latest/
    - always pin to specific version for consistency
  • Initialize after script loads (bottom of body)
  • For production: Use npm + bundler instead
Why not CDN for production?
  • ❌ Network dependency (breaks offline)
  • ❌ No version locking (CDN could change)
  • ❌ Slower (no bundler optimization)
  • ❌ No tree-shaking
  • ✅ Use npm for production:
    npm install mapbox-gl@^3.0.0

模式:脚本标签+内联初始化
⚠️ 注意: 此模式仅用于原型开发。生产环境应用应使用npm+打包工具进行版本控制和离线构建。
html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mapbox GL JS - No Bundler</title>

    <!-- Mapbox GL JS CSS -->
    <!-- Replace 3.x.x with latest version from https://docs.mapbox.com/mapbox-gl-js/ -->
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v3.x.x/mapbox-gl.css"
      rel="stylesheet"
    />

    <style>
      body {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        margin: 0;
        padding: 0;
      }
      #map-container {
        height: 100%;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div id="map-container"></div>

    <!-- Mapbox GL JS -->
    <!-- Replace 3.x.x with latest version from https://docs.mapbox.com/mapbox-gl-js/ -->
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.x.x/mapbox-gl.js"></script>

    <script>
      // Set access token
      mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN_HERE';

      let map;

      function initMap() {
        map = new mapboxgl.Map({
          container: 'map-container',
          center: [-71.05953, 42.3629],
          zoom: 13
        });

        map.on('load', () => {
          console.log('Map is loaded');
        });
      }

      // Initialize when page loads
      initMap();
    </script>
  </body>
</html>
关键点:
  • ⚠️ 仅用于原型开发 - 不推荐用于生产环境
  • 3.x.x
    替换为Mapbox文档中的具体版本(例如
    3.7.0
  • 不要使用
    /latest/
    - 始终固定到具体版本以确保一致性
  • 在脚本加载完成后初始化(放在body底部)
  • 生产环境:使用npm + 打包工具替代
为什么生产环境不推荐使用CDN?
  • ❌ 依赖网络(离线时无法使用)
  • ❌ 无法锁定版本(CDN内容可能变更)
  • ❌ 加载速度慢(无打包工具优化)
  • ❌ 不支持树摇
  • ✅ 生产环境使用npm:
    npm install mapbox-gl@^3.0.0

Token Management Patterns

令牌管理模式

Environment Variables (Recommended)

环境变量(推荐方式)

Different frameworks use different prefixes for client-side environment variables:
Framework/BundlerEnvironment VariableAccess Pattern
Vite
VITE_MAPBOX_ACCESS_TOKEN
import.meta.env.VITE_MAPBOX_ACCESS_TOKEN
Next.js
NEXT_PUBLIC_MAPBOX_TOKEN
process.env.NEXT_PUBLIC_MAPBOX_TOKEN
Create React App
REACT_APP_MAPBOX_TOKEN
process.env.REACT_APP_MAPBOX_TOKEN
Angular
environment.mapboxAccessToken
Environment files (
environment.ts
)
Vite .env file:
bash
VITE_MAPBOX_ACCESS_TOKEN=pk.eyJ1...
Next.js .env.local file:
bash
NEXT_PUBLIC_MAPBOX_TOKEN=pk.eyJ1...
Important:
  • ✅ Always use environment variables for tokens
  • ✅ Never commit
    .env
    files to version control
  • ✅ Use public tokens (pk.*) for client-side apps
  • ✅ Add
    .env
    to
    .gitignore
  • ✅ Provide
    .env.example
    template for team
.gitignore:
.env
.env.local
.env.*.local
.env.example:
bash
VITE_MAPBOX_ACCESS_TOKEN=your_token_here

不同框架/打包工具对客户端环境变量使用不同的前缀:
框架/打包工具环境变量名称访问方式
Vite
VITE_MAPBOX_ACCESS_TOKEN
import.meta.env.VITE_MAPBOX_ACCESS_TOKEN
Next.js
NEXT_PUBLIC_MAPBOX_TOKEN
process.env.NEXT_PUBLIC_MAPBOX_TOKEN
Create React App
REACT_APP_MAPBOX_TOKEN
process.env.REACT_APP_MAPBOX_TOKEN
Angular
environment.mapboxAccessToken
环境文件(
environment.ts
Vite .env文件:
bash
VITE_MAPBOX_ACCESS_TOKEN=pk.eyJ1...
Next.js .env.local文件:
bash
NEXT_PUBLIC_MAPBOX_TOKEN=pk.eyJ1...
重要提示:
  • ✅ 始终使用环境变量存储令牌
  • ✅ 绝不要将
    .env
    文件提交到版本控制系统
  • ✅ 客户端应用使用公钥(pk.*)
  • ✅ 将
    .env
    添加到
    .gitignore
  • ✅ 为团队提供
    .env.example
    模板
.gitignore:
.env
.env.local
.env.*.local
.env.example:
bash
VITE_MAPBOX_ACCESS_TOKEN=your_token_here

Mapbox Search JS Integration

Mapbox Search JS集成

Search Box Component Pattern

搜索框组件模式

Install dependency:
bash
npm install @mapbox/search-js-react      # React
npm install @mapbox/search-js-web        # Vanilla/Vue/Svelte
Note: Both packages include
@mapbox/search-js-core
as a dependency. You only need to install
-core
directly if building a custom search UI.
React Search Pattern:
jsx
import { SearchBox } from '@mapbox/search-js-react';

// Inside component:
<SearchBox
  accessToken={accessToken}
  map={mapRef.current} // Pass map instance
  mapboxgl={mapboxgl} // Pass mapboxgl library
  value={inputValue}
  onChange={(value) => setInputValue(value)}
  proximity={centerCoordinates} // Bias results near center
  marker // Show marker for selected result
/>;
Key configuration options:
  • accessToken
    : Your Mapbox public token
  • map
    : Map instance (must be initialized first)
  • mapboxgl
    : The mapboxgl library reference
  • proximity
    :
    [lng, lat]
    to bias results geographically
  • marker
    : Boolean to show/hide result marker
  • placeholder
    : Search box placeholder text
安装依赖:
bash
npm install @mapbox/search-js-react      # React
npm install @mapbox/search-js-web        # 原生/Vue/Svelte
注意: 两个包都依赖
@mapbox/search-js-core
。仅当构建自定义搜索UI时才需要直接安装
-core
包。
React搜索模式:
jsx
import { SearchBox } from '@mapbox/search-js-react';

// Inside component:
<SearchBox
  accessToken={accessToken}
  map={mapRef.current} // Pass map instance
  mapboxgl={mapboxgl} // Pass mapboxgl library
  value={inputValue}
  onChange={(value) => setInputValue(value)}
  proximity={centerCoordinates} // Bias results near center
  marker // Show marker for selected result
/>;
关键配置选项:
  • accessToken
    : 你的Mapbox公钥
  • map
    : 地图实例(必须先初始化)
  • mapboxgl
    : Mapboxgl库引用
  • proximity
    :
    [经度, 纬度]
    用于地理范围偏好搜索结果
  • marker
    : 布尔值,控制是否显示结果标记
  • placeholder
    : 搜索框占位文本

Positioning Search Box

搜索框定位

Absolute positioning (overlay):
jsx
<div
  style={{
    position: 'absolute',
    top: 10,
    right: 10,
    zIndex: 10,
    width: 300
  }}
>
  <SearchBox {...props} />
</div>
Common positions:
  • Top-right:
    top: 10px, right: 10px
  • Top-left:
    top: 10px, left: 10px
  • Bottom-left:
    bottom: 10px, left: 10px

绝对定位(覆盖层):
jsx
<div
  style={{
    position: 'absolute',
    top: 10,
    right: 10,
    zIndex: 10,
    width: 300
  }}
>
  <SearchBox {...props} />
</div>
常见位置:
  • 右上角:
    top: 10px, right: 10px
  • 左上角:
    top: 10px, left: 10px
  • 左下角:
    bottom: 10px, left: 10px

Common Mistakes to Avoid

需避免的常见错误

❌ Mistake 1: Forgetting to call map.remove()

❌ 错误1:忘记调用map.remove()

javascript
// BAD - Memory leak!
useEffect(() => {
  const map = new mapboxgl.Map({ ... })
  // No cleanup function
}, [])
javascript
// GOOD - Proper cleanup
useEffect(() => {
  const map = new mapboxgl.Map({ ... })
  return () => map.remove()  // ✅ Cleanup
}, [])
Why: Every Map instance creates WebGL contexts, event listeners, and DOM nodes. Without cleanup, these accumulate and cause memory leaks.

javascript
// BAD - Memory leak!
useEffect(() => {
  const map = new mapboxgl.Map({ ... })
  // No cleanup function
}, [])
javascript
// GOOD - Proper cleanup
useEffect(() => {
  const map = new mapboxgl.Map({ ... })
  return () => map.remove()  // ✅ Cleanup
}, [])
原因: 每个Map实例都会创建WebGL上下文、事件监听器和DOM节点。如果不清理,这些资源会不断累积导致内存泄漏。

❌ Mistake 2: Initializing map in render

❌ 错误2:在渲染函数中初始化地图

javascript
// BAD - Infinite loop in React!
function MapComponent() {
  const map = new mapboxgl.Map({ ... })  // Runs on every render
  return <div />
}
javascript
// GOOD - Initialize in effect
function MapComponent() {
  useEffect(() => {
    const map = new mapboxgl.Map({ ... })
  }, [])
  return <div />
}
Why: React components re-render frequently. Creating a new map on every render causes infinite loops and crashes.

javascript
// BAD - Infinite loop in React!
function MapComponent() {
  const map = new mapboxgl.Map({ ... })  // Runs on every render
  return <div />
}
javascript
// GOOD - Initialize in effect
function MapComponent() {
  useEffect(() => {
    const map = new mapboxgl.Map({ ... })
  }, [])
  return <div />
}
原因: React组件会频繁重渲染。每次渲染都创建新地图会导致无限循环和崩溃。

❌ Mistake 3: Not storing map instance properly

❌ 错误3:未正确存储地图实例

javascript
// BAD - map variable lost between renders
function MapComponent() {
  useEffect(() => {
    let map = new mapboxgl.Map({ ... })
    // map variable is not accessible later
  }, [])
}
javascript
// GOOD - Store in useRef
function MapComponent() {
  const mapRef = useRef()
  useEffect(() => {
    mapRef.current = new mapboxgl.Map({ ... })
    // mapRef.current accessible throughout component
  }, [])
}
Why: You need to access the map instance for operations like adding layers, markers, or calling
remove()
.

javascript
// BAD - map variable lost between renders
function MapComponent() {
  useEffect(() => {
    let map = new mapboxgl.Map({ ... })
    // map variable is not accessible later
  }, [])
}
javascript
// GOOD - Store in useRef
function MapComponent() {
  const mapRef = useRef()
  useEffect(() => {
    mapRef.current = new mapboxgl.Map({ ... })
    // mapRef.current accessible throughout component
  }, [])
}
原因: 你需要访问地图实例来执行添加图层、标记或调用
remove()
等操作。

❌ Mistake 4: Wrong dependency array in useEffect

❌ 错误4:useEffect依赖数组错误

javascript
// BAD - Re-creates map on every render
useEffect(() => {
  const map = new mapboxgl.Map({ ... })
  return () => map.remove()
})  // No dependency array

// BAD - Re-creates map when props change
useEffect(() => {
  const map = new mapboxgl.Map({ center: props.center, ... })
  return () => map.remove()
}, [props.center])
javascript
// GOOD - Initialize once
useEffect(() => {
  const map = new mapboxgl.Map({ ... })
  return () => map.remove()
}, [])  // Empty array = run once

// GOOD - Update map property instead
useEffect(() => {
  if (mapRef.current) {
    mapRef.current.setCenter(props.center)
  }
}, [props.center])
Why: Map initialization is expensive. Initialize once, then use map methods to update properties.

javascript
// BAD - Re-creates map on every render
useEffect(() => {
  const map = new mapboxgl.Map({ ... })
  return () => map.remove()
})  // No dependency array

// BAD - Re-creates map when props change
useEffect(() => {
  const map = new mapboxgl.Map({ center: props.center, ... })
  return () => map.remove()
}, [props.center])
javascript
// GOOD - Initialize once
useEffect(() => {
  const map = new mapboxgl.Map({ ... })
  return () => map.remove()
}, [])  // Empty array = run once

// GOOD - Update map property instead
useEffect(() => {
  if (mapRef.current) {
    mapRef.current.setCenter(props.center)
  }
}, [props.center])
原因: 地图初始化开销很大。只需初始化一次,然后使用地图方法更新属性。

❌ Mistake 5: Hardcoding token in source code

❌ 错误5:在源代码中硬编码令牌

javascript
// BAD - Token exposed in source code
mapboxgl.accessToken = 'pk.eyJ1IjoiZXhhbXBsZSIsImEiOiJjbGV4YW1wbGUifQ.example';
javascript
// GOOD - Use environment variable
mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;
Why: Tokens in source code get committed to version control and exposed publicly. Always use environment variables.

javascript
// BAD - Token exposed in source code
mapboxgl.accessToken = 'pk.eyJ1IjoiZXhhbXBsZSIsImEiOiJjbGV4YW1wbGUifQ.example';
javascript
// GOOD - Use environment variable
mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;
原因: 源代码中的令牌会被提交到版本控制系统并公开泄露。始终使用环境变量。

❌ Mistake 6: Not handling Angular SSR

❌ 错误6:未处理Angular SSR

typescript
// BAD - Crashes during server-side rendering
ngOnInit() {
  import('mapbox-gl').then(mapboxgl => {
    this.map = new mapboxgl.Map({ ... })
  })
}
typescript
// GOOD - Check platform first
ngOnInit() {
  if (!isPlatformBrowser(this.platformId)) {
    return  // Skip map init during SSR
  }

  import('mapbox-gl').then(mapboxgl => {
    this.map = new mapboxgl.Map({ ... })
  })
}
Why: Mapbox GL JS requires browser APIs (WebGL, Canvas). Angular Universal (SSR) will crash without platform check.

typescript
// BAD - Crashes during server-side rendering
ngOnInit() {
  import('mapbox-gl').then(mapboxgl => {
    this.map = new mapboxgl.Map({ ... })
  })
}
typescript
// GOOD - Check platform first
ngOnInit() {
  if (!isPlatformBrowser(this.platformId)) {
    return  // Skip map init during SSR
  }

  import('mapbox-gl').then(mapboxgl => {
    this.map = new mapboxgl.Map({ ... })
  })
}
原因: Mapbox GL JS需要浏览器API(WebGL、Canvas)。如果不进行平台检查,Angular Universal(SSR)会崩溃。

❌ Mistake 7: Missing CSS import

❌ 错误7:缺少CSS导入

javascript
// BAD - Map renders but looks broken
import mapboxgl from 'mapbox-gl';
// Missing CSS import
javascript
// GOOD - Import CSS for proper styling
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
Why: The CSS file contains critical styles for map controls, popups, and markers. Without it, the map appears broken.

javascript
// BAD - Map renders but looks broken
import mapboxgl from 'mapbox-gl';
// Missing CSS import
javascript
// GOOD - Import CSS for proper styling
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
原因: CSS文件包含地图控件、弹出窗口和标记的关键样式。没有它,地图显示会异常。

Next.js Specific Patterns

Next.js特定模式

App Router (Recommended)

App Router(推荐)

typescript
'use client'  // Mark as client component

import { useRef, useEffect } from 'react'
import mapboxgl from 'mapbox-gl'
import 'mapbox-gl/dist/mapbox-gl.css'

export default function Map() {
  const mapRef = useRef<mapboxgl.Map>()
  const mapContainerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (!mapContainerRef.current) return

    mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN!

    mapRef.current = new mapboxgl.Map({
      container: mapContainerRef.current,
      center: [-71.05953, 42.36290],
      zoom: 13
    })

    return () => mapRef.current?.remove()
  }, [])

  return <div ref={mapContainerRef} style={{ height: '100vh' }} />
}
Key points:
  • Must use
    'use client'
    directive
    (maps require browser APIs)
  • Use
    process.env.NEXT_PUBLIC_*
    for environment variables
  • Type
    mapRef
    properly with TypeScript
typescript
'use client'  // Mark as client component

import { useRef, useEffect } from 'react'
import mapboxgl from 'mapbox-gl'
import 'mapbox-gl/dist/mapbox-gl.css'

export default function Map() {
  const mapRef = useRef<mapboxgl.Map>()
  const mapContainerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (!mapContainerRef.current) return

    mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN!

    mapRef.current = new mapboxgl.Map({
      container: mapContainerRef.current,
      center: [-71.05953, 42.36290],
      zoom: 13
    })

    return () => mapRef.current?.remove()
  }, [])

  return <div ref={mapContainerRef} style={{ height: '100vh' }} />
}
关键点:
  • 必须使用
    'use client'
    指令
    (地图需要浏览器API)
  • 使用
    process.env.NEXT_PUBLIC_*
    环境变量
  • 使用TypeScript正确为
    mapRef
    指定类型

Pages Router (Legacy)

Pages Router(旧版)

typescript
import dynamic from 'next/dynamic'

// Dynamically import to disable SSR for map component
const Map = dynamic(() => import('../components/Map'), {
  ssr: false,
  loading: () => <p>Loading map...</p>
})

export default function HomePage() {
  return <Map />
}
Key points:
  • Use
    dynamic
    import with
    ssr: false
  • Provide loading state
  • Map component itself follows standard React pattern

typescript
import dynamic from 'next/dynamic'

// Dynamically import to disable SSR for map component
const Map = dynamic(() => import('../components/Map'), {
  ssr: false,
  loading: () => <p>Loading map...</p>
})

export default function HomePage() {
  return <Map />
}
关键点:
  • 使用
    dynamic
    导入并设置
    ssr: false
  • 提供加载状态
  • 地图组件本身遵循标准React模式

Style Configuration

样式配置

Default Center and Zoom Guidelines

默认中心和缩放级别指南

Recommended defaults:
  • Center:
    [-71.05953, 42.36290]
    (Boston, MA) - Mapbox HQ
  • Zoom:
    13
    for city-level view
Zoom level guide:
  • 0-2
    : World view
  • 3-5
    : Continent/country
  • 6-9
    : Region/state
  • 10-12
    : City view
  • 13-15
    : Neighborhood
  • 16-18
    : Street level
  • 19-22
    : Building level
Customizing for user location:
javascript
// Use browser geolocation
if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition((position) => {
    map.setCenter([position.coords.longitude, position.coords.latitude]);
    map.setZoom(13);
  });
}

推荐默认值:
  • 中心坐标
    [-71.05953, 42.36290]
    (马萨诸塞州波士顿 - Mapbox总部)
  • 缩放级别
    13
    (城市级视图)
缩放级别参考:
  • 0-2
    :全球视图
  • 3-5
    :大洲/国家视图
  • 6-9
    :地区/州视图
  • 10-12
    :城市视图
  • 13-15
    :社区视图
  • 16-18
    :街道级视图
  • 19-22
    :建筑级视图
根据用户位置自定义:
javascript
// Use browser geolocation
if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition((position) => {
    map.setCenter([position.coords.longitude, position.coords.latitude]);
    map.setZoom(13);
  });
}

Testing Patterns

测试模式

Unit Testing Maps

地图单元测试

Mock mapbox-gl:
javascript
// vitest.config.js or jest.config.js
export default {
  setupFiles: ['./test/setup.js']
};
javascript
// test/setup.js
vi.mock('mapbox-gl', () => ({
  default: {
    Map: vi.fn(() => ({
      on: vi.fn(),
      remove: vi.fn(),
      setCenter: vi.fn(),
      setZoom: vi.fn()
    })),
    accessToken: ''
  }
}));
Why: Mapbox GL JS requires WebGL and browser APIs that don't exist in test environments. Mock the library to test component logic.

Mock mapbox-gl:
javascript
// vitest.config.js or jest.config.js
export default {
  setupFiles: ['./test/setup.js']
};
javascript
// test/setup.js
vi.mock('mapbox-gl', () => ({
  default: {
    Map: vi.fn(() => ({
      on: vi.fn(),
      remove: vi.fn(),
      setCenter: vi.fn(),
      setZoom: vi.fn()
    })),
    accessToken: ''
  }
}));
原因: Mapbox GL JS需要WebGL和浏览器API,这些在测试环境中不存在。Mock该库以测试组件逻辑。

When to Use This Skill

何时使用本Skill

Invoke this skill when:
  • Setting up Mapbox GL JS in a new project
  • Integrating Mapbox into a specific framework
  • Debugging map initialization issues
  • Adding Mapbox Search functionality
  • Implementing proper cleanup and lifecycle management
  • Converting between frameworks (e.g., React to Vue)
  • Reviewing code for Mapbox integration best practices
在以下场景调用本Skill:
  • 在新项目中设置Mapbox GL JS
  • 在特定框架中集成Mapbox
  • 调试地图初始化问题
  • 添加Mapbox Search功能
  • 实现正确的清理和生命周期管理
  • 在不同框架间转换(例如React转Vue)
  • 审查Mapbox集成代码的最佳实践

Related Skills

相关Skill

  • mapbox-cartography: Map design principles and styling
  • mapbox-token-security: Token management and security
  • mapbox-style-patterns: Common map style patterns
  • mapbox-cartography:地图设计原则与样式
  • mapbox-token-security:令牌管理与安全
  • mapbox-style-patterns:常见地图样式模式

Resources

参考资源