📝 注意
本記事はAIの補助を受けて編集しています。
📚 目次
- 0. はじめに:Lighthouse 95点でもユーザーが「遅い」と言う理由
- 1. 計測ツールの全体像
- 2. Networkパネル – リソース読み込みの最適化
- 3. Performanceパネル – ランタイム解析
- 4. React DevTools Profiler – 再レンダリングの最適化
- 5. Memoryパネル – メモリリークの発見
- 6. Coverageパネル – 未使用コードの削除
- 7. Renderingパネル – レイアウトシフトとペイントのデバッグ
- 8. ラボ計測とフィールド計測 – DevToolsから実ユーザーへ
- 9. まとめと次回予告
0. はじめに:Lighthouse 95点でもユーザーが「遅い」と言う理由
こんな経験はありませんか?
- 再レンダリングやメモ化、キャッシュ戦略を徹底的に最適化したのに Lighthouseのスコアは良いのにユーザーから「遅い」と報告される
- ローカル環境では爆速なのに リモートのユーザーだけが遅いと感じる
- 原因を特定するのに何週間も無駄にした ことがある
もしそうなら、問題はコードではなく 計測プロセス にあります。
計測なしの最適化は、単なる当て推量に過ぎません。
Part 17 では次の問いに答えます:
「Chrome DevToolsとReact DevToolsをどう使えば、体系的にボトルネックを発見し、当て推量をやめられるのか?」
以下のツールを中心に解説します:
- Network → リソース読み込み
- Performance → メインスレッドのランタイム(レイアウト、ペイント、ロングタスク)
- React Profiler → コンポーネントのライフサイクル、再レンダリング
- Memory → メモリリーク
- Coverage → 未使用コード
- Rendering → 表示レイヤー、GPU
各セクションにはTypeScriptの実例と分かりやすいダイアグラムを添えています。
1. 計測ツールの全体像
Chrome DevToolsは単なるツールではなく、パフォーマンス診断のエコシステムです。
各パネルは異なる質問に答えます。間違ったパネルを使うと、大量のデータに埋もれてしまいます。
💡 初心者向けヒント:従来のPerformanceパネルより Performance Insights パネルの方が視覚的に分かりやすいので、まずはこちらから試すことをお勧めします。
2. Networkパネル – リソース読み込みの最適化
目的:どのリソースが遅いのか、なぜ遅いのか、どうすれば速くなるのかを特定する。
2.1. ウォーターフォール – リクエストの旅
ウォーターフォールチャートはNetworkパネルで最も重要なツールで、各リクエストの開始から完了までの過程を表示します。
| 色 | フェーズ | 意味 |
|---|---|---|
| 薄緑 (Queueing) | 待機中 | ブラウザが他のリクエストを処理中、接続数制限 |
| 濃緑 (Stalled) | リソース待ち | 同じドメインへのリクエストが多い可能性 |
| 灰色 (DNS Lookup) | IPアドレス検索 | 繰り返し発生するなら dns-prefetch を検討 |
| 黄色 (Initial connection) | TCP接続 | 重要なオリジンには preconnect を検討 |
| 緑 (TTFB) | 最初の応答待ち | 緑のバーが大きい → サーバー/バックエンドが遅い |
| 青 (Content Download) | コンテンツダウンロード | 青のバーが大きい → リソースが重すぎる |
素早い読み方:
- 緑のバー(TTFB)が通常より大きい:バックエンドの問題。CDN、キャッシュ、DBクエリの最適化はバックエンドの担当。
- 青のバー(ダウンロード)が大きい:リソースが重すぎる → Brotli/Gzip圧縮、WebP/AVIF画像、コード分割を検討。
タイミング以外にも、Headersタブで以下を確認:
-
cache-controlとetag– リソースが適切にキャッシュされているか -
content-encoding– BrotliやGzipが有効か -
cf-cache-status(Cloudflare)など – CDNのヒット/ミス状況
TypeScriptの例:
// /api/products が5MBのデータを返す場合
// ウォーターフォールで青いバーが非常に長くなる → ページネーションまたは圧縮が必要
2.2. スロットリング – 実際の環境をシミュレート
ローカルマシンは高速です。問題はユーザーの3G/4G環境やミドルレンジ端末です。Networkパネルのドロップダウンから「Slow 3G」などを選んで実際の遅さを体験しましょう。同様に、Performanceパネルで CPUスロットリング(4x slowdown) も有効にしてください。開発用マシンは実際の端末より遥かに高速だからです。
2.3. イニシエーター – 誰がこのリクエストを発生させたか
Initiator列を見ると、どのコードがリクエストを発生させたかが分かります。ホバーやクリックでスタックトレースを確認でき、スクリプト起因のリクエストを特定するのに役立ちます。
Networkパネルのまとめ:まず 緑のバー(サーバー遅延) または 青のバー(リソース過大) を探し、スロットリングで実環境を再現する。キャッシュヘッダやCDNヒット率も確認すること。
3. Performanceパネル – ランタイム解析
目的:ページ読み込み中やインタラクション中にメインスレッドで何が起きているかを理解する。
3.1. フレームグラフの読み方
フレームグラフはPerformanceパネルの中核です。横に長いほど時間がかかり、色で処理の種類が分かります。
| 色 | 意味 |
|---|---|
| 黄色 | JavaScript(コード実行、関数呼び出し) |
| 紫色 | レンダリング(レイアウト、スタイル再計算) |
| 緑 | ペインティング(ピクセル描画) |
3.2. よくある問題の特定
a) ロングタスク(>50ms) – 黄色や紫色の塊が50msを超えると、UIのカクつきやINP低下の原因に。Googleは各タスクを50ms以内に収めることを推奨。超える場合は setTimeout、requestIdleCallback(ただしクリティカルな操作には不向き。ブラウザサポートやタイミング保証に注意)、Web Worker、またはReactの useTransition/useDeferredValue などでの分割を検討。
b) 強制同期レイアウト(レイアウトスラッシング) – スタイル変更の直後に offsetHeight や getBoundingClientRect() を読むと、ブラウザが即座にレイアウトを強制計算する。フレームグラフ上では黄色の塊の中に紫色のブロックが挟まったパターンで現れる。
c) CLS(レイアウトシフト) – Performanceパネルには「LS」とラベルの付いたひし形の紫色のマーカーが表示される。クリックで影響範囲とズレた要素を確認できる。原因として多いのは:画像のサイズ未指定、フォントサイズ変更、広告の後から挿入。
3.3. Core Web Vitalsマーカー
PerformanceパネルはLCP、CLS、INPなどのWeb Vitalsに関連するマーカーを表示できる。ただし表示方法はChromeのバージョンによって異なる。これらを最初の手がかりとして使い、詳細はフレームグラフで確認する。
3.4. プロファイルの正しい記録方法
記録方法:赤いRecordボタンを押して操作するか、「Reload and Profile」(横に赤い点が付いた再読み込みボタン)でページ読み込み全体を記録する。**「Screenshots」**を有効にすると画像付きで分かりやすい。**CPUスロットリング(4x slowdown)**も忘れずに。
Performanceパネルのまとめ:カクつきの原因を突き止めるのがこのパネルの役割。黄色(JS)が長すぎるならタスク分割、紫色(レイアウト)が頻発するなら
requestAnimationFrameの使用を検討、LSマーカーがあれば画像やフォントのサイズ指定を確認。
4. React DevTools Profiler – 再レンダリングの最適化
目的:どのコンポーネントが頻繁に/長く再レンダリングされているか、その理由を特定する。
4.1. React Profilerのフレームグラフ
React Profilerにも独自のフレームグラフがある。横のバーの長さがレンダリング時間(自身の関数実行+子のレンダリング時間)を表す。
- バーが異常に長い(>16ms):そのコンポーネントのレンダリングが遅い。
- バーが細かく多数出現:頻繁に再レンダリングされている。
4.2. 「Why did this render?」で無駄な再レンダリングを発見
Profilerでコンポーネントをクリックすると、再レンダリングの原因が表示される:
- Props changed: propsが変わった(参照が変わっただけで中身が同じ場合も含む)
- State changed: 内部stateが変わった
- Parent re-rendered: 親が再レンダリングされた
- Hooks changed: カスタムフックの戻り値が変わった
TypeScriptの例 – useDeferredValue で入力を優先:
import { useState, useDeferredValue, useMemo } from 'react';
function ProductList({ products }: { products: Product[] }) {
const [filter, setFilter] = useState('');
const deferredFilter = useDeferredValue(filter);
const filtered = useMemo(() => {
return products.filter(p => p.name.includes(deferredFilter));
}, [products, deferredFilter]);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ProductGrid products={filtered} />
</div>
);
}
長いリストの場合は 仮想化(react-window, react-virtualized) も検討する。React 19以降では React Compiler が徐々に導入されつつあるが、現時点では本番適用前に慎重な評価が必要。
4.3. ハイライト更新で再レンダリングを可視化
React DevToolsで ⚛️ Components → Settings → General → “Highlight updates when components render.” を有効にすると、再レンダリングのたびにコンポーネントが色付きでハイライトされる。
React DevTools Profilerのまとめ:フレームグラフで遅い/頻繁なコンポーネントを見つけ、「Why did this render?」で原因を特定し、ハイライト機能でリアルタイムに観察する。目で確認する前に最適化を始めてはいけない。
5. Memoryパネル – メモリリークの発見
目的:メモリリークを発見し、メモリ使用量を最適化する。
5.1. Memoryパネルが必要なタイミング
- Chromeのタスクマネージャー(Shift+Esc)でメモリ使用量が時間とともに増加する。
- 長時間使用後にタブがクラッシュする。
- GC(ガベージコレクション)頻発でパフォーマンスが徐々に低下する。
5.2. ヒープスナップショット – デタッチDOMとその他のリーク発見
デタッチDOM(ツリーから削除されたがJSから参照されているDOMノード)はメモリリークの一般的な原因。
手順:
- Memoryパネルを開き、「Heap snapshot」を選択。
- 「Take snapshot」をクリック。
- 操作(モーダルを開いて閉じるなど)を繰り返し、2回目のスナップショットを撮る。
- 2つのスナップショットを比較し、「Detached DOM tree」を見つける。
Detachedと表示されたノードをクリックすると参照チェーンが表示され、どの変数がまだ参照を保持しているか(イベントリスナーが削除されていない、クロージャがDOMを保持しているなど)が分かる。
Reactアプリでよくあるリークの例:
useEffect(() => {
const id = setInterval(() => {
fetch('/api/status');
}, 5000);
// ❌ クリーンアップがない → コンポーネントがアンマウントされてもインターバルが動き続ける
}, []);
修正:
useEffect(() => {
const id = setInterval(() => {
fetch('/api/status');
}, 5000);
return () => clearInterval(id);
}, []);
他にも、WebSocket未クローズ、サブスクリプション未解除、Observer未切断などもリーク原因となる。
Memoryパネルのまとめ:メモリ使用量が増え続ける場合、ヒープスナップショットを繰り返し取得し「Detached DOM tree」を探す。インターバル、サブスクリプション、Observerのクリーンアップ漏れも確認する。
6. Coverageパネル – 未使用コードの削除
目的:使用されていないJS/CSSを見つけて削除し、転送量を減らす。
6.1. 使い方
Coverageパネルを開く(More tools → Coverage)。Recordボタンを押し、ページをリロードしていくつか操作する。停止すると、各ファイルの未使用コードの割合が表示される。
- 赤いバー:未使用コード
- 青いバー:使用コード
例:大きなCSSファイルの大半が未使用なら、PurgeCSSなどを検討する。
6.2. モダンな手法(コンテキスト別)
- 従来のCSSプロジェクト:PurgeCSS、UnCSS
-
Tailwind CSS:
content設定を適切にし、実際に使うクラスのみ生成 -
Vite/Webpack/Next.js:バンドルアナライザ(
vite-bundle-visualizer,webpack-bundle-analyzer,@next/bundle-analyzer)と組み合わせ、Tree Shakingや動的インポートを活用
Coverageパネルのまとめ:実際に使われていないコードを発見するのに有用だが、バンドルアナライザやビルド設定と組み合わせて根本的に削除する必要がある。
7. Renderingパネル – レイアウトシフトとペイントのデバッグ
目的:リフロー、リペイント、レイヤーを可視化する。
7.1. レンダリングツールの有効化
DevToolsで More tools → Rendering を開く。以下の機能が使える:
- Paint flashing:ブラウザがリペイントするたびに緑色に光る。スクロール時に画面全体が光るのはリペイント過多のサイン。
-
Layer borders:各レイヤーにオレンジの枠を表示。アニメーションする要素に枠がなければ、
transform/opacityを優先し、will-changeは必要最小限に(多用するとGPUメモリを消費)。 - Layout Shift Regions:レイアウトシフト(CLS)が発生した領域を紫色でハイライトし、影響を受けた要素を特定しやすくする。
7.2. 画像のレイアウトシフト修正例
<!-- ❌ CLS発生 -->
<img src="hero.jpg" alt="Hero" />
<!-- ✅ CLSなし -->
<img src="hero.jpg" alt="Hero" width="1200" height="600" />
CSSのaspect-ratioを使う場合(attr()は広くサポートされていないため、静的な値を使う):
.image-container {
aspect-ratio: 2 / 1;
}
Renderingパネルのまとめ:アニメーションがカクつくならPaint flashingとLayer bordersを確認。
transform/opacityを優先する。CLSが疑われるならLayout Shift Regionsを有効にし、画像やフォントのサイズを指定する。
8. ラボ計測とフィールド計測 – DevToolsから実ユーザーへ
Chrome DevToolsはラボ環境(ローカルマシン、高速ネットワーク、理想的な設定)でのデバッグに適している。しかし本番環境はまったく異なる(ユーザーのネットワーク、多様なデバイス、実際のキャッシュ状態)。
フィールドデータ(実ユーザーデータ)を収集するツール:
| ツール | データ種別 | 用途 |
|---|---|---|
| Chrome UX Report (CrUX) | 地域別集計 | 国・デバイス別のCore Web Vitalsを確認 |
| Google Analytics / gtag | Web Vitalsレポート | LCP, INP, CLSの時系列変動を追跡 |
| Sentry Performance | RUM + エラー | パフォーマンス低下をスタックトレースと共に検出 |
| Datadog RUM, New Relic | 高度なフィールドメトリクス | パーセンタイル分析、セグメント絞り込み、アラート |
ゴールデンルール:ラボ(DevTools)で最適化し、フィールド(RUM)で検証する。ローカルだけで最適化しても実ユーザーの問題を外している可能性がある。
9. まとめと次回予告
| パネル | 使用タイミング | 主なアクション |
|---|---|---|
| Network | ページ読み込み遅延 | ウォーターフォール、スロットリング、キャッシュ/CDNヘッダ確認 |
| Performance | カクつき、フレーム落ち | フレームグラフ、ロングタスク、レイアウトスラッシング、CPUスロットリング |
| React DevTools Profiler | Reactの再レンダリング過多 | フレームグラフ、「Why did this render?」、ハイライト更新 |
| Memory | メモリ増加、クラッシュ | ヒープスナップショット、デタッチDOM、インターバル等のクリーンアップ確認 |
| Coverage | バンドルサイズ過大 | 未使用コード計測、バンドルアナライザ併用 |
| Rendering | アニメーションカクつき、CLS | ペイントフラッシング、レイヤーボーダー、レイアウトシフト領域 |
| フィールド監視 | 本番環境がローカルと異なる | CrUX、GA、Sentry、Datadog等のRUM |
👉 次回予告 (Part 18):
[Frontend Performance - Part 18] Core Web Vitals完全攻略:LCP・INP・CLSの実践的改善
