---
name: project-wic-perf-map-bundle-split-2026-05-29
description: "2026-05-29 WIC ホームページの 'map 起動が遅い + もっさり' を解消. 原因 = globe.gl + maplibre-gl + three.js が静的 import で 2.9 MB 単一 bundle に hoisted されていた. 修正 = dynamic import + IntersectionObserver + preconnect. 初期ブロッキング JS 2.9 MB → 48 KB (~60 倍小)."
metadata: 
  node_type: memory
  type: project
  originSessionId: f11c7f03-2bde-43d2-8e6c-060b5abea7b4
---

## 問題 (藤本さん報告)
2026-05-29 藤本さん: 「https://world-wide-datacenter.pages.dev/ を開くと地図が起動するのが遅いのと、 全体的に表示がもっさりしている」.

★ URL note: world-wide-datacenter.pages.dev は旧 project だが、 今日 wrangler deploy + git push で両 project 反映済 (WebFetch verify: 旧 URL も最新 nav 含む).

## 原因 (build 出力 grep で確定)
`dist/_astro/hoisted.CF81JQMP.js` = **2.9 MB** に以下が単一 bundle 化:
- globe.gl (~1.8 MB)
- maplibre-gl (~1.1 MB)
- three.js (~500 KB, globe.gl 依存)
- pmtiles / protomaps-themes-base
- 加えて DashboardHome の inline JSON 群で HTML も 599 KB

合計初期 download ~3.5 MB がメインスレッドをブロックし、 first paint と map 起動を同時に遅らせていた.

★ 直接原因 = `src/components/WorldGlobe.astro` の top-level 静的 import:
```ts
import Globe from 'globe.gl';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
```
これらは Astro/Vite の hoisted bundle に merge されるため、 init() の遅延フックをどれだけ作っても無効化されていた.

## 修正内容 (commit `9a51c63` push 済)

### (1) Dynamic imports (最大効果)
`WorldGlobe.astro` 内で:
- 静的 import を削除
- `loadMapLibs()` 関数を追加して内部で `await Promise.all([import('globe.gl'), import('maplibre-gl')])` + CSS dynamic import
- `init()` の冒頭で `await loadMapLibs()` を呼ぶ
- module-level `let Globe: any; let maplibregl: any;` で既存コードを書き換え不要に

### (2) IntersectionObserver で defer init
`startWhenVisible()` を新設し、 map container が 400 px 以内に近づくまで `init()` を呼ばない. rootMargin で十分早めに開始するので first-tile 体感遅延は出ない. 古いブラウザ fallback (eager init) もあり.

### (3) Preconnect / dns-prefetch
`Base.astro` の `<head>` に追加:
- `<link rel="preconnect" href="https://tiles.openfreemap.org" crossorigin />`
- `<link rel="preconnect" href="https://unpkg.com" crossorigin />` (three-globe earth-night.jpg + earth-topology.png 用)
- `<link rel="dns-prefetch" href="https://s3.tradingview.com" />`
- `<link rel="dns-prefetch" href="https://www.tradingview-widget.com" />`

## 効果 (build 後実測)

| 指標 | Before | After | 削減率 |
|---|---|---|---|
| **初期ブロッキング JS** (hoisted) | **2.9 MB** | **48 KB** | **~60×** |
| Globe.gl (async chunk) | bundled | 1.8 MB | lazy |
| MapLibre (async chunk) | bundled | 1.1 MB | lazy |
| MapLibre CSS chunk | bundled | 別 chunk | lazy |
| 初期 HTML (index.html) | 599 KB | 599 KB | 未対応 (次回候補) |

## ★ Step 2 (同日続編 commit `1af5976` push 済) — 3D globe lazy init
藤本さん「やはり地図の読み込み速度を改善できないか?」 への追加対応.

### 観察
Step 1 で初期ブロッキング JS は 48 KB に削減できたが、 map 自体は依然 globe.gl 1.8 MB + maplibre-gl 1.1 MB + 地球テクスチャ ~1 MB = **3 MB 弱を全 load しないと表示完了しない**. ★ **default は 2D mode なのに 3D globe を常時構築していた** のが残り遅さの主原因.

### 修正
- `loadMapLibs()` を `loadMaplibre()` + `loadGlobeGl()` に分割
- `init()` は `loadMaplibre()` だけを await
- 3D Globe 構築 (Globe instance + earth textures + atmosphere + polygons + labels + controls) を新 `initGlobeAsync()` に wrap
- `switchMode('3d')` で初めて `await initGlobeAsync()` を呼ぶ (idempotent)
- `switchMode` を async 化 (2D path は同期, void で呼ぶ)
- `globe` / `globeControls` 参照を rotate/labels/borders/resize/applyFilter で null guard
- `countries-50m.geojson` fetch は globe block の上に hoist (flatmap 通貨/指標 overlay でも使用)
- `activeFilter` 宣言を hoist (initGlobeAsync が初 3D 時に参照)

### Step 2 効果 (2D mode に留まるユーザー = default)
| 項目 | サイズ | 配信 |
|---|---|---|
| globe.gl JS | ~1.8 MB | **3D toggle 押下時のみ** |
| earth-night.jpg + earth-topology.png (unpkg) | ~1 MB | 同上 |
| **合計 deferred** | **~2.8 MB** | ★ 2D-only user は永遠に load しない |
| 初回 map JS (2D mode) | maplibre-gl ~1.1 MB | + hoisted 48 KB |

### Step 1 + Step 2 累積効果
- 改善前: 2.9 MB 単一 bundle + globe textures = 初回 ~3.5 MB blocking
- Step 1 後: 48 KB blocking + 2.9 MB lazy chunk (IntersectionObserver)
- Step 2 後: 48 KB blocking + **1.1 MB lazy** (maplibre のみ) + 2.8 MB deferred to 3D click
- **2D-only ユーザーの total map cost: 3.5 MB → 1.15 MB (~67% 削減)**

### Honest scope
- 3D toggle 初回押下時は globe.gl + textures download が走るので「初回 3D は遅い」 感は残る (これは structural)
- 重要なのは default (2D) 体験が大幅に軽くなったこと
- countries-50m.geojson (2.2 MB) は 2D / 3D 両方で必要なので解消しない (次のターゲット候補)

## 次回候補 (まだ手付かず)
**Priority 3 (HTML 削減)**: inline JSON ~266 KB を external file 化
- `country-detail` (80 KB) → `/data/country-detail.json` + fetch
- `news-data` (77 KB) → `/data/news.json` + fetch
- `indicator-data` (63 KB) → `/data/indicators.json` + fetch
- HTML が 599 KB → ~330 KB に
- Browser cache + parallel fetch でさらに高速化

ただし今回 (1)(2)(3) の Priority 1+4 だけで体感は劇的改善するはずなので、 まず藤本さんに試して頂き、 まだもっさり感が残れば Priority 3 着手. **No-rush principle 順守**.

## CF deploy 状況
- wrangler direct: `https://7e1809e3.world-investment-center.pages.dev` (preview) → main alias `https://world-investment-center.pages.dev` 反映
- 旧 `world-wide-datacenter.pages.dev` も git push 経由で反映 (両 project が今日両方更新)

## 関連
- [[project-wic-buildout-session-2026-05-25]] (γ OpenFreeMap vector tiles 切替)
- [[project-wic-session-2026-05-26]] (CF deploy = wrangler direct 運用 rule)
- [[project-wic-sources-and-brokers-pages-2026-05-29]] (同日先行作業, 同 deploy)
- 今回触らなかった候補: external JSON / TradingView IntersectionObserver / 画像 optimize / critical CSS / SSR partial hydration
