はじめに
Critical Rendering Path(CRP) とは、ブラウザが HTML・CSS・JavaScript を画面上のピクセルに変換する一連の処理手順です。このパイプラインを最適化することで First Contentful Paint(FCP) や Largest Contentful Paint(LCP) を大幅に改善できます。本記事では DOM / CSSOM 構築からレイアウト、ペイント、コンポジットまでの各段階を解説し、実践的な最適化手法を紹介します。
第1段階:DOM 構築
ブラウザが HTML バイトを受信すると、以下の変換が行われます:
- バイト → 文字: 生バイトを文字にデコード
- トークン化: 文字をトークン(
StartTag、EndTagなど)に変換 - 字句解析: トークンをノードに変換
- DOM ツリー: 親子関係を持つノードツリーを構築
<!-- このHTMLから html > head + body > h1 + p のDOMツリーが生成される -->
<!DOCTYPE html>
<html>
<head><title>ページ</title></head>
<body>
<h1>こんにちは</h1>
<p>世界</p>
</body>
</html>
最適化: HTML サイズを最小化し、適切なサーバー設定で早期配信、ストリーミング(Transfer-Encoding: chunked)を活用。
第2段階:CSSOM 構築
CSS はデフォルトで レンダリングブロック します。ブラウザはすべての CSS をダウンロード・解析するまでレンダリングを開始しません。CSS バイトも HTML と同様の変換パイプラインを経て CSS Object Model(CSSOM) を生成します。
body { font-size: 16px; }
h1 { color: blue; }
各 CSS ルールはセレクターにマッチングされ、優先度(詳細度)が計算されます。
最適化:
- クリティカル CSS を
<head>にインライン化 - 非クリティカル CSS は
media="print"やloading="lazy"で遅延読み込み - ミニファイと ツリーシェイキング で未使用 CSS を除去
第3段階:レンダーツリー
レンダーツリー は DOM と CSSOM を結合したもので、可視要素のみ を含みます。display: none の要素や <head> の子要素は除外されます。各ノードは計算済みのスタイルを持ちます。
DOM CSSOM
| |
+----+-----+
|
レンダーツリー
(可視ノード + 計算済みスタイル)
第4段階:レイアウト(リフロー)
ブラウザは各レンダーツリーノードの ジオメトリ(位置とサイズ) を計算します。これはビューポートから始まる トップダウン の処理です。パーセンテージ幅や Flexbox、Grid を使用した要素はコンテナとの相対計算が必要です。
レイアウトスラッシングの原因
スタイル書き込み後にレイアウトプロパティ(offsetHeight など)を読み取ると、同一フレーム内で強制的に 同期レイアウトが複数回発生 します:
// ❌ 悪い例:ループごとに強制レイアウト
for (let i = 0; i < boxes.length; i++) {
const width = box.offsetWidth; // 読み取り(レイアウトトリガー)
box.style.width = (width + 10) + 'px'; // 書き込み
}
// ✅ 良い例:読み取りを先にバッチ処理
const widths = boxes.map(b => b.offsetWidth); // 読み取り
boxes.forEach((b, i) => b.style.width = (widths[i] + 10) + 'px'); // 書き込み
最適化:
- レイアウトプロパティは先に一括読み取り
- アニメーションには
transformとopacityを使用(コンポジターのみ処理) contain: layout style paintでサブツリーを分離
第5段階:ペイント
ブラウザはテキスト、色、画像、ボーダー、シャドウなどの ピクセルを塗りつぶします。これはラスタライズを伴うため高コストです。別の コンポジターレイヤー にある要素は独立してペイントできます。
ペイントのトリガー
color、background-color、visibility、outlineの変更- シャドウやテキスト装飾の追加
第6段階:コンポジット(合成)
ブラウザはペイント済みのレイヤーを GPU 上で 合成 し、最終フレームを生成します。コンポジットはレイアウトやペイントに比べて安価です。目標はほとんどの変更をコンポジットのみで処理できるようにすることです。
コンポジター専用プロパティ
| プロパティ | レイアウト発生 | ペイント発生 | コンポジットのみ |
|---|---|---|---|
transform | なし | なし | はい |
opacity | なし | なし | はい |
width | あり | あり | いいえ |
top/left | あり | あり | いいえ |
ブロッキングリソースと非ブロッキングリソース
| リソース | デフォルトの動作 | 最適化 |
|---|---|---|
CSS(<link>) | レンダリングブロック | クリティカルCSSをインライン化 |
JS <script> | パーサーブロック(DOM) | async または defer を使用 |
JS defer | 非ブロッキング、HTML解析後に実行 | 実行順序は維持 |
JS async | 非ブロッキング、ダウンロード後に即実行 | 実行順序は保証なし |
<!-- ✅ ベストプラクティス -->
<script async src="analytics.js"></script>
<script defer src="app.js"></script>
<link rel="stylesheet" href="critical.css" />
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'" />
Lighthouse によるCRP測定
Lighthouse は CRP に特化した複数の監査項目を提供します:
- Eliminate render-blocking resources — ブロッキングリソースの特定
- Defer offscreen images — ペイント範囲の削減
- Minimize main-thread work — 長時間タスクの特定
- Reduce JavaScript execution time — パーサーブロック対策
実行: npx lighthouse https://example.com --preset=desktop
まとめ
Critical Rendering Path の最適化には、DOM / CSSOM 構築、レンダーツリー形成、レイアウト、ペイント、コンポジットという一連の流れを理解することが不可欠です。クリティカル CSS のインライン化、async/defer によるスクリプト制御、バッチ処理によるレイアウトスラッシングの防止、そしてコンポジター専用プロパティの活用が主要な戦略です。Lighthouse による定期的な監査で CRP のボトルネックを特定し、継続的に改善を進めましょう。
