「URLを入力して Enter を押した瞬間から、Webページが滑らかに表示されるまで、ブラウザ内部で何が起きているのか?」――そんな疑問を持ったことはありませんか?
その答えが Critical Rendering Path(CRP) です。
CRPとは、ブラウザが HTML・CSS・JavaScript を解析し、最終的に画面上のピクセルとして描画するまでの一連のプロセスを指します。
CRPの最適化は、単に「表示速度を速くする」ことではありません。
ユーザーにとって重要なコンテンツを、できるだけ早く届けるために、限られたリソースの読み込み順序を設計する――そんな“レンダリング最適化の技術”とも言えます。
1. CRPという「描画エンジン」はどのように動くのか?
まずは基本的なパイプラインを見てみましょう。
HTML → DOM
↘
Render Tree → Layout → Paint → Composite
↗
CSS → CSSOM
それでは、各ステップを順番に見ていきます。
ステップ1: HTML Parsing と DOM の構築
サーバーから最初のバイトデータを受け取った瞬間から、HTML Parser は動き始めます。
ブラウザ内部では、次のような変換プロセスが進行しています。
Bytes → Characters → Tokens → Nodes → DOM
参考: Constructing the Object Model | web.dev
-
Tokenization(トークン化)
ブラウザは HTML を「開始タグ」「終了タグ」「テキスト」などのトークンへ分解し、ドキュメント構造を解析します。 -
段階的な構築(Incremental Parsing)
DOM は、Parser がノードを発見した時点で逐次的に構築されます。
つまり、ブラウザは HTML ファイル全体の読み込み完了を待たずに処理を進めます。 -
Preload Scanner
これは Parser と並行して動作する“先読み専用の補助スキャナ”です。
メインの Parser がブロックされている間でも、残りの HTML を先読みし、画像・CSS・JavaScript などのリソースを事前に検出して早期ロードを行います。
ステップ2: CSS Parsing と CSSOM の構築
ブラウザが <link> や <style> タグを検出すると、CSS Parser が CSS を解析し、CSS Object Model(CSSOM) を構築します。
-
Render-blocking(レンダリングブロック)
HTML と異なり、CSSOM は段階的に構築できません。
CSS には Cascade(カスケード)の仕組みがあり、後から定義されたスタイルが前のスタイルを上書きする可能性があるためです。そのため、ブラウザは「最終的なスタイル」を確定するまで描画を開始できません。
-
JavaScript との関係
script がスタイル情報へアクセスしようとした場合、ブラウザは CSSOM の構築完了まで JavaScript の実行を待機します。
ステップ3: Render Tree の生成
DOM と CSSOM の両方が完成すると、ブラウザはそれらを組み合わせて Render Tree を生成します。
- このツリーには、実際に画面へ表示される要素のみ が含まれます
-
display: noneの要素や、<head>・<script>のような非表示要素は Render Tree に含まれません
ステップ4: Layout(Reflow)
ブラウザは、デバイスのビューポート(viewport)を基準にして、Render Tree 内の各ノードのジオメトリ情報(x・y 座標、width・height)を計算します。
- ウィンドウサイズやコンテンツの変更が発生すると、この処理が再実行されることがあります。
これを Reflow と呼びます。
ステップ5: Paint(描画)
Layout の結果をもとに、ブラウザは色・境界線・シャドウなどの描画命令を、実際のピクセルへ変換してメモリ上の Layer(レイヤー) に描画します。
ステップ6: Composite(レイヤー合成)
これは Critical Rendering Path の最後のステップです。
Paint の段階で、ブラウザはすでに描画済みの複数の Layer を保持しています。
ここで Compositor(専用スレッド)が、それらの Layer を GPU に渡し、stacking context や z-index の順序に従って合成(composite)します。
最終的に、ユーザーが見る画面がここで完成します。
重要なポイント:
-
Composite は、レンダリングパイプラインの中でも 最も低コストな処理 です
(Composite のみで済む場合は非常に高速)。 -
この処理は Compositor Thread と GPU 上で実行されるため、Main Thread をブロックしません。
参考: Inside look at modern web browser | developer.chrome.com
2. CSS の変更と CRP 各ステップの関係
変更する CSS プロパティによって、ブラウザが再実行するレンダリング処理の範囲は異なります。
レベル1: Reflow / Relayout(最も重い・CPU コストが高い)
ブラウザは次の処理をすべて再実行します。
Reflow (Layout) → Repaint → Re-composite
これは、要素のサイズ・位置・レイアウト構造に影響する変更で発生します。
小さな変更でも、周囲の要素へ連鎖的に影響し、Reflow Cascade が発生する場合があります。
代表的な CSS プロパティ:
- width, height, margin, padding, border
- display, position, float, top, left, right, bottom
- font-size, line-height, font-weight
- flex, grid 関連プロパティ, gap など
レベル2: Repaint
ブラウザは次の処理を再実行します。
Repaint → Re-composite
これは、色や見た目のみを変更し、サイズや位置には影響しない場合に発生します。
代表的な CSS プロパティ:
- color, background-color, background-image
- border-radius, box-shadow, outline
- text-decoration, font-style, visibility
レベル3: Re-composite(最も軽い — アニメーションに最適)
Layout を変更せず、ピクセルの再描画も不要な場合、ブラウザは GPU 上の既存 Layer を再合成するだけで済みます。
代表的な CSS プロパティ:
- transform: translate(), scale(), rotate(), skew()
- opacity
- filter, backdrop-filter
- will-change: transform, opacity
→ これが、60fps の滑らかなアニメーションで transform や opacity が推奨される理由です。
3. JavaScript は CRP にどう影響するのか?
JavaScript は Critical Rendering Path に大きな影響を与えます。
デフォルトでは、ブラウザが次のような script タグを検出すると:
<script src="app.js"></script>
次の順序で処理を行います。
- HTML Parsing を停止
- JavaScript ファイルをダウンロード
- Script を実行
- その後で HTML Parsing を再開
これは次のように呼ばれます。
Parser-blocking JavaScript
その理由は、JavaScript が Parsing 中の DOM を参照・変更できるためです。たとえ DOM 操作を行わないスクリプトであっても、ブラウザはその可能性を排除できないため、すべての同期スクリプトで Parsing を停止します。
JavaScript は CSS によってブロックされることもある
見落とされがちな重要ポイントとして、同期 JavaScript は CSSOM の完成を待たされる場合があります。
特に script が以下のような style 情報へアクセスする場合です。
getComputedStyle()offsetWidth.style
この場合、ブラウザは CSSOM が完成するまで JavaScript の実行を開始できません。
→ つまり、次のような 二重のボトルネック が発生します。
- CSS → Render-blocking
- JS → Parser-blocking
- JS は場合によって CSSOM 完了待ちにもなる
async と defer はどう解決するのか?
async
<script async src="app.js"></script>
async を使うと:
- HTML Parsing と並行してダウンロードされる
- ダウンロード完了後、即座に execute される
ただし:
- script 間の実行順序は保証されない
- execute 時に HTML Parser を一時停止する可能性がある
主に以下に適しています。
- analytics
- ads
- tracking script
- third-party script
defer
<script defer src="app.js"></script>
defer も HTML Parsing と並行してダウンロードされますが:
- execute は HTML Parsing 完了後
- script の実行順序が保持される
通常、以下のようなケースでは defer が最適です。
- application script
- React / Vue アプリ
- main business logic
多くのアプリケーション script では、defer を使うのが推奨されます。
4. Parser-blocking と Render-blocking の違い
この 2 つは非常に混同されやすい概念ですが、パフォーマンスへの影響は異なります。
| 項目 | Parser-blocking(主に JS) | Render-blocking(主に CSS) |
|---|---|---|
| 本質 | HTML Parsing の停止 | 画面への描画(Paint)の停止 |
| 影響 | DOM の構築が完了せず、後続リソースの発見も遅れる | ユーザーに白画面や FOUC が見える可能性がある |
| 目的 | Parsing 中の DOM を JS が変更して整合性を壊さないようにする | Flash of Unstyled Content(FOUC)を防ぐ |
| 主な最適化方法 |
async / defer の使用、または script を body の最後へ配置 |
Critical CSS の inline 化、重要 CSS の preload |
重要なポイント:
Parser-blocking(同期 JavaScript)は、間接的に Render-blocking も引き起こします。
なぜなら、DOM Parsing が完了していない状態では、ブラウザはコンテンツを正しく Render できないためです。
参考: Optimising the front end for the browser | hackernoon.com