mapbox-integration-patterns について
WebアプリにMapbox GL JSを組み込む開発者向けのスキルです。フレームワーク別のライフサイクル管理、トークン設定、検索連携、SSR対応のパターンを扱います。
主な機能
- 01React、Vue、Svelte、Angularの連携パターン
- 02マップインスタンスのライフサイクル管理
- 03環境変数によるトークン管理
- 04Mapbox Search JS連携
- 05Next.jsとSSRの注意点
活用シーン
- 01Reactアプリへのインタラクティブマップ追加
- 02各種フレームワークでのマップ初期化
- 03Mapboxアクセストークンの安全な設定
- 04Angularのサーバーサイドレンダリング時の読み込み対応
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mapbox/ mcp-devkit-server --skill mapbox-integration-patternsインストール後、Claude Code で/mapbox-integration-patternsを入力して読み込まれたか確認します。
詳細
Mapbox 統合パターンのスキル
このスキルでは、さまざまなフレームワークで Mapbox GL JS をウェブアプリケーションに統合するための公式パターンを紹介します。これらのパターンは Mapbox の create-web-app スキャフォールディングツールに基づいており、本番環境で利用できるベストプラクティスを示しています。
バージョン要件
Mapbox GL JS
推奨: v3.x(最新)
- 最小: v3.0.0
- v3.x を推奨する理由: 最新の API、パフォーマンスの向上、活発な開発
- v2.x: 引き続きサポートされていますが、非推奨のパターンが含まれます(下記の移行に関する注意を参照)
npm 経由でのインストール(本番環境向けに推奨):
npm install mapbox-gl@^3.0.0 # Installs latest v3.x
CDN(プロトタイピング専用):
<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"
/>
⚠️ 本番アプリでは CDN ではなく npm を使用してください。バージョンの一貫性とオフラインでのビルドが保証されます。
フレームワークの要件
React:
- 最小:19+(
create-web-appの現在の実装) - 推奨:最新の 19.x
Vue:
- 最小:3.x(Composition API を推奨)
- Vue 2.x:Options 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
検索統合に必要:
npm install @mapbox/search-js-react@^1.0.0 # React
npm install @mapbox/search-js-web@^1.0.0 # Other frameworks
バージョン移行に関する注意
v2.x から v3.x への移行:
accessTokenを Map コンストラクターに渡せるようになりました(推奨)- TypeScript の型が改善されました
- ツリーシェイキングのサポートが向上しました
- コアの初期化パターンに破壊的変更はありません
例:
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: '...'
});
基本原則
Mapbox GL JS の統合では、必ず次の点を守ってください。
- 適切なライフサイクルフックで地図を初期化する
- 地図のインスタンスをコンポーネントの状態に保存する(レンダリングのたびに再作成しない)
- メモリーリークを防ぐため、クリーンアップ時には必ず
map.remove()を呼び出す - トークンを安全に管理する(環境変数を使用する)
- CSS をインポートする:
import 'mapbox-gl/dist/mapbox-gl.css'
フレームワーク別のパターン
React の統合
パターン:useRef + クリーンアップ付き useEffect
注: 以下の例では
create-web-appで使用されているバンドラーの Vite を使用しています。Create React App を使用する場合は、import.meta.env.VITE_MAPBOX_ACCESS_TOKENをprocess.env.REACT_APP_MAPBOX_TOKENに置き換えてください。他のバンドラーについては、トークン管理パターンのセクションを参照してください。
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を使用する - 空の deps
[]を指定してuseEffect内で初期化する map.remove()を呼び出すクリーンアップ関数を必ず返す- レンダリング内で地図を初期化しない(無限ループの原因になります)
React + Search JS:
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 の統合
パターン:mounted + unmounted のライフサイクルフック
<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として保存する map.remove()を呼び出すunmounted()フックを必ず実装する
Svelte の統合
パターン:onMount + onDestroy
<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}でコンテナーをバインドするmap.remove()を呼び出すonDestroyを必ず実装する- Svelte では
accessTokenを Map コンストラクターに直接渡せます
Angular の統合
パターン:SSR 対応の ngOnInit + ngOnDestroy
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):
<div #mapContainer style="height: 100vh; width: 100%"></div>
要点:
@ViewChildを使って地図コンテナーを参照する- 初期化前に
isPlatformBrowserを確認する(SSR 対応) - SSR の問題を避けるため、
mapbox-glを動的にインポートする ngOnInit()ライフサイクルフックで初期化するmap.remove()を呼び出すngOnDestroy()を必ず実装するmap.on('error', ...)でエラーを処理する
Vanilla JavaScript(Vite 使用)
パターン:モジュールのインポートと初期化関数
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:
<div id="map-container" style="height: 100vh;"></div>
要点:
- 地図をモジュールスコープの変数に保存する
- すぐに初期化するか、DOMContentLoaded イベント時に初期化する
- 初期化後の処理には 'load' イベントをリッスンする
Vanilla JavaScript(バンドラーなし - CDN)
パターン:インライン初期化を伴うスクリプトタグ
⚠️ 注意: このパターンはプロトタイピング専用です。本番アプリでは、バージョン管理とオフラインビルドのために npm/バンドラーを使用してください。
<!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>
<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>
<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>
主なポイント:
- ⚠️ プロトタイピング専用 - 本番環境では推奨されません
- Mapbox のドキュメントから特定のバージョン(例:
3.7.0)を選び、3.x.xを置き換えてください /latest/は使用しないでください - 一貫性を保つため、必ず特定のバージョンに固定してください- スクリプトの読み込み後に初期化してください(body の末尾)
- 本番環境では、代わりに npm + バンドラーを使用してください
本番環境で CDN を使わない理由
- ❌ ネットワークへの依存(オフラインでは動作しません)
- ❌ バージョンを固定できない(CDN の内容が変更される可能性があります)
- ❌ 低速(バンドラーによる最適化がありません)
- ❌ ツリーシェイキングがありません
- ✅ 本番環境では npm を使用してください:
npm install mapbox-gl@^3.0.0
トークン管理のパターン
環境変数(推奨)
フレームワークごとに、クライアント側の環境変数に異なるプレフィックスを使用します。
| フレームワーク/バンドラー | 環境変数 | アクセス方法 |
|---|---|---|
| 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 ファイル:
VITE_MAPBOX_ACCESS_TOKEN=pk.eyJ1...
Next.js の .env.local ファイル:
NEXT_PUBLIC_MAPBOX_TOKEN=pk.eyJ1...
重要:
- ✅ トークンには必ず環境変数を使用してください
- ✅
.envファイルをバージョン管理にコミットしないでください - ✅ クライアント側アプリには公開トークン(pk.*)を使用してください
- ✅
.envを.gitignoreに追加してください - ✅ チーム用に
.env.exampleのテンプレートを用意してください
.gitignore:
.env
.env.local
.env.*.local
.env.example:
VITE_MAPBOX_ACCESS_TOKEN=your_token_here
Mapbox Search JS の統合
検索ボックスコンポーネントのパターン
依存関係をインストールします:
npm install @mapbox/search-js-react # React
npm install @mapbox/search-js-web # Vanilla/Vue/Svelte
注意: どちらのパッケージにも依存関係として@mapbox/search-js-coreが含まれています。カスタム検索 UI を構築する場合のみ、-coreを直接インストールしてください。
React の検索パターン:
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:地理的に結果を絞り込むための[lng, lat]marker:結果マーカーを表示するかどうかを指定するブール値placeholder:検索ボックスのプレースホルダーテキスト
検索ボックスの配置
絶対位置指定(オーバーレイ):
<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
よくある間違いとその回避方法
❌ 間違い 1:map.remove()の呼び出し忘れ
// BAD - Memory leak!
useEffect(() => {
const map = new mapboxgl.Map({ ... })
// No cleanup function
}, [])
// GOOD - Proper cleanup
useEffect(() => {
const map = new mapboxgl.Map({ ... })
return () => map.remove() // ✅ Cleanup
}, [])
理由: Map インスタンスを作成するたびに、WebGL コンテキスト、イベントリスナー、DOM ノードが生成されます。クリーンアップを行わないと、これらが蓄積してメモリーリークの原因になります。
❌ 間違い 2:render 内でのマップの初期化
// BAD - Infinite loop in React!
function MapComponent() {
const map = new mapboxgl.Map({ ... }) // Runs on every render
return <div />
}
// GOOD - Initialize in effect
function MapComponent() {
useEffect(() => {
const map = new mapboxgl.Map({ ... })
}, [])
return <div />
}
理由: React コンポーネントは頻繁に再レンダリングされます。レンダリングのたびに新しいマップを作成すると、無限ループやクラッシュが発生します。
❌ 間違い 3:マップのインスタンスを適切に保存していない
// BAD - map variable lost between renders
function MapComponent() {
useEffect(() => {
let map = new mapboxgl.Map({ ... })
// map variable is not accessible later
}, [])
}
// GOOD - Store in useRef
function MapComponent() {
const mapRef = useRef()
useEffect(() => {
mapRef.current = new mapboxgl.Map({ ... })
// mapRef.current accessible throughout component
}, [])
}
理由: レイヤーやマーカーの追加、remove()の呼び出しなどの操作には、マップのインスタンスにアクセスする必要があります。
❌ 間違い 4:useEffect の依存配列が間違っている
// 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])
// 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])
理由: マップの初期化にはコストがかかります。初期化は一度だけ行い、その後はマップのメソッドを使ってプロパティを更新してください。
❌ 間違い 5:ソースコードへのトークンのハードコーディング
// BAD - Token exposed in source code
mapboxgl.accessToken = 'pk.eyJ1IjoiZXhhbXBsZSIsImEiOiJjbGV4YW1wbGUifQ.example';
// GOOD - Use environment variable
mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;
理由: ソースコード内のトークンはバージョン管理にコミットされ、公開されてしまいます。必ず環境変数を使用してください。
❌ 間違い 6:Angular SSR に対応していない
// BAD - Crashes during server-side rendering
ngOnInit() {
import('mapbox-gl').then(mapboxgl => {
this.map = new mapboxgl.Map({ ... })
})
}
// 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)は、プラットフォームチェックがないとクラッシュします。
❌ 間違い 7:CSS のインポートがない
// BAD - Map renders but looks broken
import mapboxgl from 'mapbox-gl';
// Missing CSS import
// GOOD - Import CSS for proper styling
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
理由: CSS ファイルには、マップコントロール、ポップアップ、マーカーに必要なスタイルが含まれています。これがないと、マップが正しく表示されません。
Next.js 固有のパターン
App Router(推奨)
'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(レガシー)
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 />
}
重要なポイント:
ssr: falseを指定してdynamicインポートを使用します- 読み込み状態を用意します
- マップコンポーネント自体は標準的な React パターンに従います
スタイル設定
デフォルトの中心位置とズームのガイドライン
推奨されるデフォルト値:
- 中心位置:
[-71.05953, 42.36290](米国マサチューセッツ州ボストン)- Mapbox 本社 - ズーム:都市レベルの表示には
13
ズームレベルの目安:
0-2:世界3-5:大陸/国6-9:地域/州10-12:都市13-15:近隣地域16-18:街路19-22:建物
ユーザーの位置に合わせたカスタマイズ:
// Use browser geolocation
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
map.setCenter([position.coords.longitude, position.coords.latitude]);
map.setZoom(13);
});
}
テストのパターン
マップの単体テスト
mapbox-gl をモックします:
// vitest.config.js or jest.config.js
export default {
setupFiles: ['./test/setup.js']
};
// 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 を必要とします。コンポーネントのロジックをテストするには、ライブラリをモックしてください。
このスキルを使用する場面
次のような場合に、このスキルを呼び出してください。
- 新しいプロジェクトで Mapbox GL JS をセットアップするとき
- 特定のフレームワークに Mapbox を統合するとき
- マップの初期化に関する問題をデバッグするとき
- Mapbox Search の機能を追加するとき
- 適切なクリーンアップとライフサイクル管理を実装するとき
- フレームワーク間で変換するとき(例:React から Vue)
- Mapbox 統合のベストプラクティスに沿ってコードをレビューするとき
関連するスキル
- mapbox-cartography:マップのデザイン原則とスタイリング
- mapbox-token-security:トークンの管理とセキュリティ
- mapbox-style-patterns:一般的なマップスタイルのパターン