mapbox-web-integration-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMapbox Integration Patterns Skill
Mapbox集成方案技能
This skill provides official patterns for integrating Mapbox GL JS into web applications across different frameworks. These patterns are based on Mapbox's scaffolding tool and represent production-ready best practices.
create-web-app本技能提供了在不同Web框架中集成Mapbox GL JS的官方方案,这些方案基于Mapbox的脚手架工具,代表了生产环境就绪的最佳实践。
create-web-appVersion 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.xCDN (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 # 安装最新v3.x版本CDN方式(仅用于原型开发):
html
<!-- 将VERSION替换为https://docs.mapbox.com/mapbox-gl-js/中的最新v3.x版本号 -->
<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:
- can now be passed to Map constructor (preferred)
accessToken - 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:
- 现在可直接传入Map构造函数(推荐方式)
accessToken - 改进了TypeScript类型定义
- 更好的Tree-shaking支持
- 核心初始化方案无破坏性变更
示例:
javascript
const token = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN; // 生产环境使用环境变量
// v2.x方案(在v3.x中仍可使用)
mapboxgl.accessToken = token;
const map = new mapboxgl.Map({ container: '...' });
// v3.x方案(推荐)
const map = new mapboxgl.Map({
accessToken: token,
container: '...'
});Core Principles
核心原则
Every Mapbox GL JS integration must:
- Initialize the map in the correct lifecycle hook
- Store map instance in component state (not recreate on every render)
- Always call on cleanup to prevent memory leaks
map.remove() - Handle token management securely (environment variables)
- Import CSS:
import 'mapbox-gl/dist/mapbox-gl.css'
所有Mapbox GL JS集成必须满足:
- 在正确的生命周期钩子中初始化地图
- 将地图实例存储在组件状态中(不要在每次渲染时重新创建)
- 清理时务必调用以防止内存泄漏
map.remove() - 安全处理令牌管理(使用环境变量)
- 导入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). If using Create React App, replacecreate-web-appwithimport.meta.env.VITE_MAPBOX_ACCESS_TOKEN. See the Token Management Patterns section for other bundlers.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' }} />;
}Key points:
- Use for both map instance and container
useRef - Initialize in with empty deps
useEffect[] - 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); // 存储地图实例
const mapContainerRef = useRef(null); // 存储DOM引用
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
});
// 关键:清理逻辑防止内存泄漏
return () => {
mapRef.current.remove();
};
}, []); // 空依赖数组 = 仅在组件挂载时执行一次
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 hook
mounted() - Access container via
this.$refs.mapContainer - Store map as
this.map - Always implement hook to call
unmounted()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
});
// 将地图实例赋值给组件属性
this.map = map;
},
// 关键:组件卸载时执行清理
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 for initialization
onMount - Bind container with
bind:this={mapContainer} - Always implement to call
onDestroymap.remove() - Can pass directly to Map constructor in Svelte
accessToken
方案: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
})
})
// 关键:组件销毁时执行清理
onDestroy(() => {
map.remove()
})
</script>
<div class="map" bind:this={mapContainer}></div>
<style>
.map {
position: absolute;
width: 100%;
height: 100%;
}
</style>关键点:
- 使用进行初始化
onMount - 通过绑定容器
bind:this={mapContainer} - 务必实现调用
onDestroymap.remove() - 在Svelte中可直接将传入Map构造函数
accessToken
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 to reference map container
@ViewChild - Check before initializing (SSR support)
isPlatformBrowser - Dynamically import to avoid SSR issues
mapbox-gl - Initialize in lifecycle hook
ngOnInit() - Always implement to call
ngOnDestroy()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> {
// 重要:检查是否在浏览器环境运行(而非SSR)
if (!isPlatformBrowser(this.platformId)) {
return;
}
try {
await this.initializeMap();
} catch (error) {
console.error('Failed to initialize map:', error);
}
}
private async initializeMap(): Promise<void> {
// 动态导入以避免SSR问题
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
});
// 处理地图错误
this.map.on('error', (e: any) => console.error('Map error:', e.error));
}
// 关键:组件销毁时执行清理
ngOnDestroy(): void {
if (this.map) {
this.map.remove();
}
}
}模板文件(map.component.html):
html
<div #mapContainer style="height: 100vh; width: 100%"></div>关键点:
- 使用获取地图容器引用
@ViewChild - 初始化前检查(支持SSR)
isPlatformBrowser - 动态导入以避免SSR问题
mapbox-gl - 在生命周期钩子中初始化
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';
// 设置访问令牌
mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;
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');
});
}
// 脚本运行时初始化地图
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 with specific version (e.g.,
3.x.x) from Mapbox docs3.7.0 - Don't use - always pin to specific version for consistency
/latest/ - 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 -->
<!-- 将3.x.x替换为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 -->
<!-- 将3.x.x替换为https://docs.mapbox.com/mapbox-gl-js/中的最新版本 -->
<script src="https://api.mapbox.com/mapbox-gl-js/v3.x.x/mapbox-gl.js"></script>
<script>
// 设置访问令牌
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');
});
}
// 页面加载完成后初始化
initMap();
</script>
</body>
</html>关键点:
- ⚠️ 仅用于原型开发 - 不推荐用于生产环境
- 将替换为Mapbox文档中的具体版本(例如
3.x.x)3.7.0 - 不要使用- 始终固定到具体版本以保证一致性
/latest/ - 在脚本加载完成后初始化(放在body底部)
- 生产环境:使用npm + 打包工具替代
为何生产环境不推荐CDN?
- ❌ 依赖网络(离线时失效)
- ❌ 无版本锁定(CDN内容可能变更)
- ❌ 加载速度慢(无打包工具优化)
- ❌ 不支持Tree-shaking
- ✅ 生产环境请使用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/Bundler | Environment Variable | Access Pattern |
|---|---|---|
| Vite | | |
| Next.js | | |
| Create React App | | |
| Angular | | Environment files ( |
Vite .env file:
bash
VITE_MAPBOX_ACCESS_TOKEN=pk.YOUR_MAPBOX_TOKEN_HERENext.js .env.local file:
bash
NEXT_PUBLIC_MAPBOX_TOKEN=pk.YOUR_MAPBOX_TOKEN_HEREImportant:
- ✅ Always use environment variables for tokens
- ✅ Never commit files to version control
.env - ✅ Use public tokens (pk.*) for client-side apps
- ✅ Add to
.env.gitignore - ✅ Provide template for team
.env.example
.gitignore:
.env
.env.local
.env.*.local.env.example:
bash
VITE_MAPBOX_ACCESS_TOKEN=your_token_here不同框架/打包工具对客户端环境变量使用不同的前缀:
| 框架/打包工具 | 环境变量名称 | 获取方式 |
|---|---|---|
| Vite | | |
| Next.js | | |
| Create React App | | |
| Angular | | 环境文件( |
Vite的.env文件示例:
bash
VITE_MAPBOX_ACCESS_TOKEN=pk.YOUR_MAPBOX_TOKEN_HERENext.js的.env.local文件示例:
bash
NEXT_PUBLIC_MAPBOX_TOKEN=pk.YOUR_MAPBOX_TOKEN_HERE重要提示:
- ✅ 始终使用环境变量存储令牌
- ✅ 绝不要将文件提交到版本控制系统
.env - ✅ 客户端应用使用公钥(pk.*开头)
- ✅ 将添加到
.env.gitignore - ✅ 为团队提供模板
.env.example
.gitignore示例:
.env
.env.local
.env.*.local.env.example示例:
bash
VITE_MAPBOX_ACCESS_TOKEN=your_token_hereMapbox 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/SvelteNote: Both packages include as a dependency. You only need to install directly if building a custom search UI.
@mapbox/search-js-core-coreReact 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:
- : Your Mapbox public token
accessToken - : Map instance (must be initialized first)
map - : The mapboxgl library reference
mapboxgl - :
proximityto bias results geographically[lng, lat] - : Boolean to show/hide result marker
marker - : Search box placeholder text
placeholder
安装依赖:
bash
npm install @mapbox/search-js-react # React框架
npm install @mapbox/search-js-web # 原生JS/Vue/Svelte注意: 两个包均依赖,仅在自定义搜索UI时才需要直接安装包。
@mapbox/search-js-core-coreReact搜索集成示例:
jsx
import { SearchBox } from '@mapbox/search-js-react';
// 组件内部使用:
<SearchBox
accessToken={accessToken}
map={mapRef.current} // 传入地图实例
mapboxgl={mapboxgl} // 传入mapboxgl库引用
value={inputValue}
onChange={(value) => setInputValue(value)}
proximity={centerCoordinates} // 优先返回地图中心附近结果
marker // 为选中结果显示标记
/>;关键配置项:
- : 你的Mapbox公钥
accessToken - : 地图实例(必须先完成初始化)
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
// 错误 - 内存泄漏!
useEffect(() => {
const map = new mapboxgl.Map({ ... })
// 无清理函数
}, [])javascript
// 正确 - 合理清理
useEffect(() => {
const map = new mapboxgl.Map({ ... })
return () => map.remove() // ✅ 清理逻辑
}, [])原因: 每个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
// 错误 - React中会导致无限循环!
function MapComponent() {
const map = new mapboxgl.Map({ ... }) // 每次渲染都会执行
return <div />
}javascript
// 正确 - 在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
// 错误 - 渲染间会丢失map变量
function MapComponent() {
useEffect(() => {
let map = new mapboxgl.Map({ ... })
// 后续无法访问map变量
}, [])
}javascript
// 正确 - 使用useRef存储
function MapComponent() {
const mapRef = useRef()
useEffect(() => {
mapRef.current = new mapboxgl.Map({ ... })
// 组件生命周期内均可访问mapRef.current
}, [])
}原因: 你需要访问地图实例来执行添加图层、标记或调用等操作。
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
// 错误 - 每次渲染都会重新创建地图
useEffect(() => {
const map = new mapboxgl.Map({ ... })
return () => map.remove()
}) // 无依赖数组
// 错误 - props变化时重新创建地图
useEffect(() => {
const map = new mapboxgl.Map({ center: props.center, ... })
return () => map.remove()
}, [props.center])javascript
// 正确 - 仅初始化一次
useEffect(() => {
const map = new mapboxgl.Map({ ... })
return () => map.remove()
}, []) // 空数组 = 仅执行一次
// 正确 - 直接更新地图属性
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.YOUR_MAPBOX_TOKEN_HERE';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
// 错误 - 令牌会暴露在源码中
mapboxgl.accessToken = 'pk.YOUR_MAPBOX_TOKEN_HERE';javascript
// 正确 - 使用环境变量
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
// 错误 - 服务端渲染时会崩溃
ngOnInit() {
import('mapbox-gl').then(mapboxgl => {
this.map = new mapboxgl.Map({ ... })
})
}typescript
// 正确 - 先检查运行平台
ngOnInit() {
if (!isPlatformBrowser(this.platformId)) {
return // 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 importjavascript
// 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
// 错误 - 地图可渲染但样式错乱
import mapboxgl from 'mapbox-gl';
// 缺失CSS导入javascript
// 正确 - 导入CSS以保证样式正常
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 directive (maps require browser APIs)
'use client' - Use for environment variables
process.env.NEXT_PUBLIC_* - Type properly with TypeScript
mapRef
typescript
'use client' // 标记为客户端组件
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' }} />
}关键点:
- 必须使用指令(地图依赖浏览器API)
'use client' - 使用获取环境变量
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 import with
dynamicssr: false - Provide loading state
- Map component itself follows standard React pattern
typescript
import dynamic from 'next/dynamic'
// 动态导入以禁用地图组件的SSR
const Map = dynamic(() => import('../components/Map'), {
ssr: false,
loading: () => <p>Loading map...</p>
})
export default function HomePage() {
return <Map />
}关键点:
- 使用导入并设置
dynamicssr: false - 提供加载状态
- 地图组件本身遵循标准React集成方案
Style Configuration
样式配置
Default Center and Zoom Guidelines
默认中心点与缩放级别指南
Recommended defaults:
- Center: (Boston, MA) - Mapbox HQ
[-71.05953, 42.36290] - Zoom: for city-level view
13
Zoom level guide:
- : World view
0-2 - : Continent/country
3-5 - : Region/state
6-9 - : City view
10-12 - : Neighborhood
13-15 - : Street level
16-18 - : Building level
19-22
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);
});
}推荐默认值:
- 中心点: (马萨诸塞州波士顿 - Mapbox总部)
[-71.05953, 42.36290] - 缩放级别: (城市级视图)
13
缩放级别参考:
- : 全球视图
0-2 - : 大洲/国家视图
3-5 - : 地区/州级视图
6-9 - : 城市视图
10-12 - : 街区视图
13-15 - : 街道级视图
16-18 - : 建筑级视图
19-22
根据用户位置自定义:
javascript
// 使用浏览器定位API
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或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
何时使用本技能
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
在以下场景调用本技能:
- 新项目中搭建Mapbox GL JS
- 在特定框架中集成Mapbox
- 调试地图初始化问题
- 添加Mapbox搜索功能
- 实现合理的清理与生命周期管理
- 跨框架迁移集成方案(例如React转Vue)
- 审查Mapbox集成代码的最佳实践
Related Skills
相关技能
- 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: 常见地图样式方案