短縮まとめ(対象別実行手順)
ここでは短時間で効果が出る優先改善と、詳細プロファイリングやCI導入への導線を示します。初心者・中級者・上級者ごとに最初に取るべき具体アクションを短くまとめます。
Contents
- 1 初心者(即効改善を最優先にしたい場合)
- 2 中級者(詳細プロファイリングで原因を特定する場合)
- 3 上級者(CI 組み込みと運用ルールを回す場合)
- 4 DevTools での CPU・フレーム・メモリ計測
- 5 Lighthouse(ラボ)と RUM(実ユーザー)の住み分け
- 6 フレームと Flame chart の読み方
- 7 トレース(Trace Events)解析と優先度付け
- 8 メモリとイメージリーク解析
- 9 レンダラー選択(HTML レンダラー vs CanvasKit)
- 10 CanvasKit の部分遅延ロードと WASM 最適化
- 11 deferred import の実践と Caveat
- 12 アセット最適化(画像・フォント・配信)
- 13 ランタイム最適化(ウィジェット設計・描画・アニメーション)
- 14 Service Worker の具体例(fetch ハンドリング)
- 15 LHCI と閾値設計(実践例)
- 16 CI フローと閾値超過時の対応
- 17 収集方針(推奨)
- 18 軽量な導入例(web‑vitals)
- 19 ビルドとサイズ解析
- 20 ローカル配信とプロファイル実行
- 21 Lighthouse / LHCI / サイズ確認
- 22 バージョン確認(測定時に必ず記録)
- 23 ケーススタディ記録テンプレート
- 24 実践チェックリスト(優先度別)
初心者(即効改善を最優先にしたい場合)
初心者はまず「計測してから最小限の手を打つ」流れを守ってください。手順は以下の順に実行します。
- release ビルドでベースラインを取得する(HTML レンダラー推奨)。
- 画像・フォントを圧縮(AVIF/WebP、woff2 サブセット)して再計測する。
- const の適用や不要な再構築の除去を行い、改善を小さく複数回に分けて PR にする。
中級者(詳細プロファイリングで原因を特定する場合)
中級者は DevTools と Lighthouse を組み合わせて原因を突き止めます。代表シナリオを定義して再現性を確保してください。
- profile ビルドで Flame chart とメモリスナップショットを取得する。
- Lighthouse を複数回実行し JSON を保存、差分で効果を評価する。
- 長時間タスク(>50ms)や 16ms フレーム超過を優先的に改善する。
上級者(CI 組み込みと運用ルールを回す場合)
上級者は自動化と運用を整え、閾値超過時の対応ルールを作ります。RUM を導入して実ユーザーを監視します。
- LHCI で PR ゲートを作り、閾値(例: LCP 90th < 2.5s、バンドル増分 < 5%)を設定する。
- web‑vitals ベースの RUM を導入し、PII 非含有・サンプリング・保存期間を決める。
- 閾値違反時は自動 Fail → Slack 通知 → 即時ロールバック/Feature‑flag 停止のフローを用意する。
測定(DevTools / Lighthouse / RUM)
まずは正確なデータを取り、条件を揃えて比較できるようにしてください。DevTools でのトレースと Lighthouse のラボ測定、RUM(web‑vitals)を組み合わせると原因特定とユーザー影響度評価が両立できます。
DevTools での CPU・フレーム・メモリ計測
DevTools は JavaScript と Flutter 側の処理を照らし合わせるのに有効です。profile ビルドで実行し、代表シナリオを録画してください。
- Profile モードでアプリを起動して DevTools の Performance を記録します。
- Flame chart で長時間タスク(50ms超)を探し、コールスタックで原因関数を特定します。
- Frame chart では Build / Layout / Paint の比率を確認し、16ms を超えるフレームに注目します。
- Memory タブでスナップショットを取得し、retained size が増加する箇所を調べます。
Lighthouse(ラボ)と RUM(実ユーザー)の住み分け
Lighthouse は比較的再現性の高いラボ評価を提供しますが、実ユーザーの分布や接続状況は RUM で補完してください。ツールのバージョンで指標定義が変わるため、計測時のバージョンを必ず保存します。
- Lighthouse(複数回実行、JSON 保存)で FCP/LCP/CLS/TBT/TTI/INP を採取します。
- RUM は web‑vitals(LCP/CLS/INP 等)で収集すると良いです。PII を含めないこと、サンプリングと保持期間を運用ルールで定めます。
- ラボと RUM の乖離はユーザ分布差(デバイス・ネットワーク)を示します。SLO はパーセンタイルで設計してください(例: LCP 90th)。
分析(トレース・フレーム・メモリ)
測定結果をもとに、何が遅いか・どの層の改善が効くかを判定します。トレースとフレーム解析でボトルネック箇所を特定し、影響度と実装工数で優先度を付けます。
フレームと Flame chart の読み方
フレームチャートでは UI の Build/Layout/Paint と JS 実行の境界を確認します。どの処理が 16ms を超えているかを見つけるのが基本です。
- 長時間タスク(50ms)で起点を特定します。JS パース・コンパイルや GC、重い Dart 側処理が典型原因です。
- Build に偏っていればウィジェット再構築の削減、Paint に偏っていれば RepaintBoundary 等の対策を検討します。
トレース(Trace Events)解析と優先度付け
トレース全体を見て、初回表示に影響する処理(パース・初期化)とインタラクションに影響する処理(長時間タスク)を分離します。
- FCP/LCP はユーザーがページが「見える」までの時間、TBT はブロッキング時間の合計、INP はインタラクションの応答性です。
- 影響度×工数で優先度を決め、パーセンタイル(例: 75th/90th/95th)で SLO を設計します。
メモリとイメージリーク解析
メモリは長期的な品質に直結します。ImageCache や未解放のリスナーが典型的なリーク源です。
- Memory スナップショットを比較して retained size と GC の挙動を確認します。
- Image のキャッシュや大きな Uint8List 等が残っていないかをチェックします。
- PaintingBinding.instance.imageCache 等の API は SDK バージョン依存なので、変更時は互換性を検証してください(後述の API 注記を参照)。
改善(ビルド / ランタイム / アセット)
特定したボトルネックに応じて、ビルド設定・ランタイム最適化・アセット最適化を組み合わせて改善します。ここでは実務で使える具体策と落とし穴を示します。
レンダラー選択(HTML レンダラー vs CanvasKit)
レンダラーは見た目と初回ロードのトレードオフです。HTML は軽量で CanvasKit は高品質だがサイズが増します。A/B テストで定量評価してください。
- まず HTML で要件を満たすか検証し、CanvasKit が必要であれば比較検証を行います。
- CanvasKit の初回バンドル増は大きいので、部分的ロード戦略や別ルート(iframe)での提供を検討してください。
- 両者は同一シナリオで Lighthouse/DevTools による比較を必ず行ってください。
CanvasKit の部分遅延ロードと WASM 最適化
CanvasKit を使う場合、WASM と関連ファイルの配布方法で初回体感が大きく変わります。WASM は CDN 配信・Brotli 圧縮が効果的です。
- 実務的な手法の一つは「主要ルートは HTML レンダラーで提供、重い描画ページだけを CanvasKit ビルドをホストした別 URL(例: /canvaskit-app)で iframe 表示する」方式です。
- WASM は Brotli 圧縮で配信し、Cache‑Control: public, max‑age=31536000, immutable のようにハッシュ資産で長期キャッシュを有効にします。
- wasm‑opt 等でさらに最適化可能だが互換性リスクがあるため、検証を厳密に行ってください。
deferred import の実践と Caveat
Dart の deferred import は有効なコード分割手法ですが、chunk 名やキャッシュ挙動に注意が必要です。
- 実装例(遅延ロード):
dart
import 'heavy_page.dart' deferred as heavy;
Future
await heavy.loadLibrary();
Navigator.push(context, MaterialPageRoute(builder: (_) => heavy.HeavyPage()));
}
- 注意点: ビルドごとにチャンクファイル名がハッシュ化されるため、Service Worker や外部参照でファイル名を固定してはいけません。チャンクの読み込み遅延を監視し、ユーザー向けのローディング UI を用意してください。
アセット最適化(画像・フォント・配信)
初回ロードの多くは画像・フォント・JS です。圧縮、サブセット、CDN 配信を組み合わせます。
- 画像: 写真は AVIF/WebP、アイコンは SVG、解像度別に準備してレスポンシブ配信。
-
フォント: woff2 でサブセット化し、index.html に preload を追加する(crossorigin 属性を忘れずに)。
html
<link rel="preload" href="/assets/fonts/Custom.woff2" as="font" type="font/woff2" crossorigin> -
配信: CDN + Brotli 優先、HTTP/2/3 を活用し静的資産はハッシュ名で長期キャッシュに設定します。
ランタイム最適化(ウィジェット設計・描画・アニメーション)
ウィジェットの再構築を抑え、描画コストを限定することでフレーム破綻を防ぎます。
- const コンストラクタの活用、ステートは局所化、広範囲 setState を避けます。
- RepaintBoundary で重いサブツリーを切り出し、CustomPainter の shouldRepaint を厳密に実装します。
- アニメーションは transform/opacity に寄せ、layout を伴うアニメーションは最小化します。
- ImageCache の調整は有効ですが、API は SDK 依存です。次節の互換性注意を参照してください。
Service Worker の具体例(fetch ハンドリング)
Service Worker はキャッシュ戦略で更新体験とパフォーマンスを両立します。以下は概念的な fetch ハンドラの例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
const CACHE_NAME = 'app-v1'; const ASSETS = [ /* build 時に生成されるハッシュ付き資産一覧 */ ]; self.addEventListener('install', event => { event.waitUntil(caches.open(CACHE_NAME).then(c => c.addAll(ASSETS))); }); self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(keys => Promise.all( keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)) )) ); }); self.addEventListener('fetch', event => { const req = event.request; const url = new URL(req.url); // index.html は network-first(更新を反映させる) if (url.pathname === '/' || url.pathname.endsWith('index.html')) { event.respondWith(fetch(req).then(r => { const copy = r.clone(); caches.open(CACHE_NAME).then(c => c.put(req, copy)); return r; }).catch(() => caches.match(req))); return; } // ハッシュ付き静的資産は cache-first event.respondWith(caches.match(req).then(resp => resp || fetch(req))); }); |
- 実運用では ASSETS をビルド出力から自動生成し、activate 時に古いキャッシュの掃除とユーザ通知(更新あり)UI を用意してください。
互換性と実装上の注意(API やツール)
以下は実務でよく問題になるポイントとその対処です。特に API 名やツール仕様は SDK/ツールのバージョン差で変わるため必ず検証してください。
-
PaintingBinding.instance.imageCache のプロパティは SDK によって差があるため、実行前に存在チェックを行ってください。例:
dart
void adjustImageCache() {
final cache = PaintingBinding.instance?.imageCache;
if (cache != null) {
try {
cache.maximumSize = 100; // SDK によっては maximumSizeBytes 等の API 名がある
} catch (e) {
// 互換性のない SDK の場合のフォールバック処理
}
}
} -
Lighthouse / TTI / 指標の扱いはバージョンで変わるので、LHCI や Lighthouse のバージョンを CI artefact として保存してください。
- CanvasKit の遅延ロードは単純ではないため、iframe を使った別ビルドによる分離や CDN ホスティングを検討すると効果的です。
- deferred import のチャンク名はビルドごとに変化するため、Service Worker や外部ツールでファイル名をハードコードしないでください。
CI と運用(LHCI / RUM / ロールバック)
CI にパフォーマンスチェックを組み込むことで回帰を防げます。閾値はプロジェクト特性で調整しますが、運用で有用な具体例を示します。
LHCI と閾値設計(実践例)
LHCI による自動判定の例と推奨閾値です。閾値はまず厳しめの「警告」→「本番閾値」へ段階的に設定します。
- 例示的な目安(パーセンタイル指定が可能なら 90th / 75th を使う):
- LCP 90th < 2500 ms
- CLS 75th < 0.10
- INP (または FID 置換) 95th < 200 ms
- TBT 75th < 300 ms
- バンドルサイズ増分(gzip/brotli ベース): PR 増分 < 5%
- LHCI は assertions を使って閾値判定できます。テストはステージ URL に対して行い、結果を JSON として保存します。
CI フローと閾値超過時の対応
CI に組み込む際の現実的な運用フロー例です。自動化とヒューマンレビューを組み合わせます。
- PR 時: ビルド → analyze-size 出力保存 → ステージにデプロイ → LHCI 実行 → アーティファクト(Lighthouse JSON, analyze-size JSON)を保存。
- 閾値超過時の自動処理例: CI Fail → PR に自動コメント(失敗差分と最重点箇所)→ Slack 通知 → 1 時間以内に担当者アサイン。
- 重大なスローダウンが検出された場合の即時対応: feature flag をオフにする、該当 PR を revert、緊急パッチを切る、などのランブックを用意します。
RUM とプライバシー設計
RUM は実ユーザーの分布を把握するため重要ですが、個人情報保護の観点から設計が必要です。
収集方針(推奨)
RUM を導入する際の推奨ポリシーです。コンプライアンスを満たすように設計してください。
- PII は送らない(ユーザーID はハッシュ化するか送らない)。IP は可能であれば集計段階で匿名化する。
- サンプリング戦略: 通常 1%〜5% をデフォルトにし、エラーや新機能ではサンプリング率を上げる。
- データ保持: 生ログは短期(例: 30 日)、集計データは中長期(例: 365 日)で保持する運用が一般的。
- 同意管理: GDPR 等の対象領域ではユーザー同意を取得した上で収集を行う(同意がない場合は測定を停止)。
軽量な導入例(web‑vitals)
|
1 2 3 4 5 6 7 8 9 10 11 |
import {getCLS, getLCP, getINP} from 'web-vitals'; function sendMetric(metric) { // PII を含めない、必要ならサンプル化して送る fetch('/rum/collect', { method: 'POST', body: JSON.stringify(metric), keepalive: true }); } getCLS(sendMetric); getLCP(sendMetric); getINP(sendMetric); |
コマンド参照(ビルド・計測・解析のコマンド集)
この節に主要なコマンドを集約します。繰り返しの説明はここで参照してください。計測時は必ずバージョンを併せて記録してください。
ビルドとサイズ解析
以下は代表的なコマンドです。--analyze-size 出力は JSON 化されるため CI に保存して差分を解析してください。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# HTML レンダラー(軽量) flutter build web --release --web-renderer=html # CanvasKit(高品質) flutter build web --release --web-renderer=canvaskit # profile ビルド(DevTools 計測用) flutter build web --profile # サイズ解析 JSON 出力 flutter build web --release --analyze-size |
ローカル配信とプロファイル実行
|
1 2 3 4 5 6 |
# 簡易 HTTP 配信(開発用) npx http-server build/web -p 8080 # Chrome で profile 実行(DevTools 接続可) flutter run -d chrome --profile --web-renderer=html |
Lighthouse / LHCI / サイズ確認
|
1 2 3 4 5 6 7 8 9 10 |
# Lighthouse 実行例(モバイルエミュレート) npx lighthouse https://staging.example.com --output=json --output-path=./lighthouse.json --emulated-form-factor=mobile --throttling-method=devtools # LHCI の簡易 autorun(CI 用) npx @lhci/cli autorun --upload.target=temporary-public-storage # gzip / brotli での配信サイズ確認 gzip -9 -c build/web/main.dart.js | wc -c brotli -q 11 -c build/web/main.dart.js | wc -c |
バージョン確認(測定時に必ず記録)
|
1 2 3 |
flutter --version # ブラウザは chrome://version を参照するか、`google-chrome --version` 等で確認 |
チェックリスト・ケーススタディテンプレート
現場で使えるチェックリストと、改善を記録するテンプレートを用意します。数値は必ず同一条件で計測してください。
ケーススタディ記録テンプレート
測定と改善の再現性を高めるため、PR に次を添付してください。
- 環境: Flutter SDK、Dart、Chrome、OS、デバイス、ネットワーク条件
- 測定方法: 使用ツール(DevTools/Lighthouse/web‑vitals)、ビルドモード、再現手順
- 変更点: 施した改善(例: deferred import、AVIF 化、RepaintBoundary 追加)
- 結果: バンドルサイズ(raw/gzip/brotli)、FCP/LCP/TBT/INP(測定条件明記)
- 所見と次の対策
実践チェックリスト(優先度別)
初動(即効、数時間〜数日)
- release ビルドでベースライン取得
- 大きな静的アセットを圧縮/サブセット化
- const の適用、不要な再構築を削除
中期(数週間)
- deferred imports の導入(重ページを遅延ロード)
- Service Worker のキャッシュ戦略見直し(index.html は network-first)
- CDN と Brotli 導入
長期(アーキテクチャ改修)
- CanvasKit と HTML の A/B テスト
- CI に LHCI と analyze-size を組み込み、PR ゲート化
- 画像配信のレスポンシブ化(Image CDN 等)
まとめと次のアクション
計測→分析→改善→CI の一連の流れを小さく回すことが最も効果的です。まずは release ビルドでベースラインを取り、初心者向けの即効策で改善効果を確認してください。中・長期的には LHCI と RUM を組み合わせ、閾値とロールバック手順を明確にして運用に落とし込んでください。
参考(公式ドキュメント)
- Flutter ドキュメント(DevTools, Web のガイド): https://docs.flutter.dev
- Lighthouse(Google): https://developer.chrome.com/docs/lighthouse/
- web‑vitals ライブラリ: https://github.com/GoogleChrome/web-vitals
各ツールや API の仕様はバージョンによって変化します。計測時には使用した Flutter / Chrome / Lighthouse / web‑vitals のバージョンを必ず記録し、差分比較を行ってください。