mapbox-web-performance-patterns
Mapbox GL JS を使った Web アプリケーションのパフォーマンス最適化パターンを提供します。初期化のウォーターフォール、バンドルサイズ、レンダリング性能、メモリ管理、Web 最適化全般をカバーし、ユーザー体験への影響度順に優先度付けされています。
description の原文を見る
Performance optimization patterns for Mapbox GL JS web applications. Covers initialization waterfalls, bundle size, rendering performance, memory management, and web optimization. Prioritized by impact on user experience.
SKILL.md 本文
Mapbox パフォーマンスパターン スキル
このスキルは、高速で効率的な Mapbox アプリケーション構築のためのパフォーマンス最適化ガイダンスを提供します。パターンはユーザー体験への影響度で優先付けされており、最も重要な改善から始まります。
**パフォーマンス哲学:**これはマイクロ最適化ではありません。すべてのユーザーセッションに影響する待ち時間、フレーム落ち、反復コストとして現れます。
優先度レベル
パフォーマンス問題はユーザー体験への影響度で優先付けされます:
- 🔴 重大 (最初に修正): 初期読込の遅延または目に見えるフレーム落ちを直接引き起こす
- 🟡 高影響: 顕著な遅延またはリソース使用量増加
- 🟢 最適化: ポーリッシュのための段階的改善
🔴 重大: 初期化ウォーターフォールの排除
**問題:**順序立った読込はカスケード遅延を作り出し、各リソースが前のものを待つ状況を生みます。
注記:最新のバンドラー (Vite、Webpack など) および ESM 動的インポートは自動的にコード分割とライブラリ読込を処理します。排除する主なウォーターフォールはデータ読込です - マップデータをマップ初期化と並行ではなく順序立てて取得します。
アンチパターン: 順序立ったデータ読込
// ❌ BAD: Data loads AFTER map initializes
async function initMap() {
const map = new mapboxgl.Map({
container: 'map',
accessToken: MAPBOX_TOKEN,
style: 'mapbox://styles/mapbox/streets-v12'
});
// Wait for map to load, THEN fetch data
map.on('load', async () => {
const data = await fetch('/api/data'); // Waterfall!
map.addSource('data', { type: 'geojson', data: await data.json() });
});
}
**タイムライン:**マップ初期化 (0.5秒) → データ取得 (1秒) = 合計1.5秒
解決策: 並行データ読込
// ✅ GOOD: Data fetch starts immediately
async function initMap() {
// Start data fetch immediately (don't wait for map)
const dataPromise = fetch('/api/data').then((r) => r.json());
const map = new mapboxgl.Map({
container: 'map',
accessToken: MAPBOX_TOKEN,
style: 'mapbox://styles/mapbox/streets-v12'
});
// Data is ready when map loads
map.on('load', async () => {
const data = await dataPromise;
map.addSource('data', { type: 'geojson', data });
map.addLayer({
id: 'data-layer',
type: 'circle',
source: 'data'
});
});
}
**タイムライン:**Max(マップ初期化、データ取得) = 合計約1秒
正確な初期ビューポート設定
// ✅ Set exact center/zoom so the map fetches the right tiles immediately
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [-122.4194, 37.7749],
zoom: 13
});
// Use 'idle' to know when the initial viewport is fully rendered
// (all tiles, sprites, and other resources are loaded; no transitions in progress)
map.once('idle', () => {
console.log('Initial viewport fully rendered');
});
ユーザーが最初に見るエリアが正確にわかっている場合、事前に center と zoom を設定することで、マップがデフォルトビューで始まった後にターゲットへパン/ズームする無駄を避けられます。
非重大機能の遅延
// ✅ Load critical features first, defer others
const map = new mapboxgl.Map({
/* config */
});
map.on('load', () => {
// 1. Add critical layers immediately
addCriticalLayers(map);
// 2. Defer secondary features
// Note: Standard style 3D buildings can be toggled via config:
// map.setConfigProperty('basemap', 'show3dObjects', false);
requestIdleCallback(
() => {
addTerrain(map);
addCustom3DLayers(map); // For classic styles with custom fill-extrusion layers
},
{ timeout: 2000 }
);
// 3. Defer analytics and non-visual features
setTimeout(() => {
initializeAnalytics(map);
}, 3000);
});
**影響:**特に地形と3Dレイヤーを遅延させる場合、インタラクティブ化までの時間が大幅に短縮されます
🔴 重大: 初期バンドルサイズの最適化
**問題:**大きなバンドルは低速ネットワークではインタラクティブ化までの時間を遅延させます。
**注記:**最新のバンドラー (Vite、Webpack など) はフレームワークベースのアプリケーション用にコード分割を自動的に処理します。以下のガイダンスは何がバンドルされ、いつバンドルされるかを最適化することに最も関連しています。
スタイル JSON バンドル影響
// ❌ BAD: Inline massive style JSON (can be 500+ KB)
const style = {
version: 8,
sources: {
/* 100s of lines */
},
layers: [
/* 100s of layers */
]
};
// ✅ GOOD: Reference Mapbox-hosted styles
const map = new mapboxgl.Map({
style: 'mapbox://styles/mapbox/streets-v12' // Fetched on demand
});
// ✅ OR: Store large custom styles externally
const map = new mapboxgl.Map({
style: '/styles/custom-style.json' // Loaded separately
});
**影響:**インラインからホスト型スタイルに移行する際、初期バンドルを30~50%削減します
🟡 高影響: マーカー数の最適化
**問題:**マーカーが多すぎるとレンダリング速度が遅くなり、インタラクション遅延が生じます。
パフォーマンス閾値
- 100未満のマーカー: HTML マーカー OK (Marker クラス)
- 100~10,000マーカー: シンボルレイヤー使用 (GPU加速)
- 10,000以上のマーカー: クラスタリング推奨
- 100,000以上のマーカー: サーバー側クラスタリングを備えたベクトルタイル
アンチパターン: 数千の HTML マーカー
// ❌ BAD: 5,000 HTML markers = 5+ second render, janky pan/zoom
restaurants.forEach((restaurant) => {
const marker = new mapboxgl.Marker()
.setLngLat([restaurant.lng, restaurant.lat])
.setPopup(new mapboxgl.Popup().setHTML(restaurant.name))
.addTo(map);
});
**結果:**5,000個の DOM 要素、遅いインタラクション、高メモリ使用量
解決策: シンボルレイヤー (GeoJSON) 使用
// ✅ GOOD: GPU-accelerated rendering, smooth at 10,000+ features
map.addSource('restaurants', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: restaurants.map((r) => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [r.lng, r.lat] },
properties: { name: r.name, type: r.type }
}))
}
});
map.addLayer({
id: 'restaurants',
type: 'symbol',
source: 'restaurants',
layout: {
'icon-image': 'restaurant',
'icon-size': 0.8,
'text-field': ['get', 'name'],
'text-size': 12,
'text-offset': [0, 1.5],
'text-anchor': 'top'
}
});
// Click handler (one listener for all features)
map.on('click', 'restaurants', (e) => {
const feature = e.features[0];
new mapboxgl.Popup().setLngLat(feature.geometry.coordinates).setHTML(feature.properties.name).addTo(map);
});
**パフォーマンス:**10,000フィーチャーが100ms未満でレンダリング
解決策: 高密度用クラスタリング
// ✅ GOOD: 50,000 markers → ~500 clusters at low zoom
map.addSource('restaurants', {
type: 'geojson',
data: restaurantsGeoJSON,
cluster: true,
clusterMaxZoom: 14, // Stop clustering at zoom 15
clusterRadius: 50 // Radius relative to tile dimensions (512 = full tile width)
});
// Cluster circle layer
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'restaurants',
filter: ['has', 'point_count'],
paint: {
'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 750, '#f28cb1'],
'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40]
}
});
// Cluster count label
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'restaurants',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-size': 12
}
});
// Individual point layer
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'restaurants',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#11b4da',
'circle-radius': 6
}
});
**影響:**50,000マーカーが60 FPS でスムーズなインタラクションで動作
サマリー: パフォーマンスチェックリスト
Mapbox アプリケーションを構築する際、順序に従ってこれらの最適化を確認してください:
🔴 重大 (最初に実施)
- マップライブラリとデータを並行で読込 (ウォーターフォール排除)
- マップコードに動的インポート使用 (初期バンドル削減)
- 非重大機能を遅延 (地形、カスタム3Dレイヤー、アナリティクス)
- 100以上のマーカーでシンボルレイヤー使用 (HTML マーカーではなく)
- 大規模データセットのビューポートベース読込実装
🟡 高影響
- マップイベントハンドラーをデバウンス/スロットル
- queryRenderedFeatures をレイヤーフィルターとバウンディングボックスで最適化
- 5MB未満は GeoJSON、20MB以上はベクトルタイル使用
- SPA で常に map.remove() をクリーンアップで呼び出す
- ポップアップインスタンスを再利用 (インタラクションごとに作成しない)
- ホバー/選択に動的レイヤーではなくフィーチャーステート使用
🟢 最適化
- データ駆動型スタイルで複数レイヤーを統合
- モバイル固有の最適化を追加 (円形レイヤー、回転無効)
- レイヤーで minzoom/maxzoom を設定して無関係なズームレベルでのレンダリング回避
- 必要な場合を除き preserveDrawingBuffer または antialias を有効にしない
測定
// Measure initial load time
console.time('map-load');
map.on('load', () => {
console.timeEnd('map-load');
// isStyleLoaded() returns true when style, sources, tiles, sprites, and models are all loaded
console.log('Style loaded:', map.isStyleLoaded());
});
// Monitor frame rate
let frameCount = 0;
map.on('render', () => frameCount++);
setInterval(() => {
console.log('FPS:', frameCount);
frameCount = 0;
}, 1000);
// Check memory usage (Chrome DevTools -> Performance -> Memory)
目標メトリクス:
- **インタラクティブ化までの時間:**3G で 2秒以下
- **フレームレート:**パン/ズーム時に 60 FPS
- **メモリ増加:**使用1時間あたり 10MB 未満
- **バンドルサイズ:**初期 500KB 未満 (マップは遅延読込)
リファレンスファイル
特定のトピックの詳細パターンについては、対応するリファレンスファイルを読み込んでください:
references/data-loading.md— GeoJSON とベクトルタイルの決定マトリックス、ビューポートベース読込、プログレッシブ読込、大規模データセット用ベクトルタイルreferences/interactions.md— デバウンス/スロットルイベント、フィーチャークエリー最適化、バッチ DOM 更新references/memory.md— マップクリーンアップパターン、ポップアップ/マーカー再利用、フィーチャーステート vs 動的レイヤーreferences/mobile.md— デバイス検出、モバイル最適化レイヤー、タッチインタラクション、コンストラクターオプションreferences/layers-styles.md— データ駆動型スタイルでレイヤーを統合、式の簡素化、ズームベース表示
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- mapbox
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/mapbox/mapbox-agent-skills / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。