ブラウザの投機的パース処理とリソース優先度

なぜ script を head に置くと表示が止まり、画像に fetchpriority を付けると LCP が縮むのか。プリロードスキャナとリソース内部優先度の原理を、効かせ方まで詳説します。

応用ブラウザパフォーマンスHTMLリソース読み込みCore Web Vitals
3つの要点
  1. HTMLパーサ本体が同期 script で止まっている間も、プリロードスキャナが先のトークンを走査して img・CSS・script などのURLを先行ダウンロードする。これが初期表示の取得を前倒しする中核。
  2. 同期 script はパースをブロックし、後続の script は先行する CSSOM 構築の完了を待つ。だから head の重い script は描画を直列に止める。
  3. ブラウザは要素種別・位置・属性からリソースに内部優先度を割り当てる。fetchpriority はその値を high/low に上書きするヒントで、特に LCP 画像の前倒しに効く。

投機的パースとは何か:メインパーサと先読みの二段構え

ブラウザがHTMLを受信してDOMを組み立てるパーサは、本質的に直列です。トークンを先頭から順に消費し、<script>(インラインまたは同期外部)に出会うとスクリプトの取得・実行が終わるまでDOM構築を止めます。素朴に実装すると、この停止中はネットワークが完全に遊んでしまい、後続に並ぶCSSや画像のダウンロードが始まりません。

これを救うのがプリロードスキャナ(preload scanner、投機的パーサ)です。メインパーサが詰まっている間、スキャナは入力バッファのまだDOM化していない先の部分を軽量に走査し、src / href を持つトークン(img、link rel=stylesheet、script src、video の poster など)からURLを抜き出して先行的に取得を開始します。DOMツリーは作らず、属性も完全には解釈しない「投機的」な読みなので、メインパーサより速く先へ進めます。

メインパーサ:  <head>……<script>―――(取得+実行で停止)―――▶ …<body><img>…
                                    ▲ここで停止しても
プリロードスキャナ: ───先のバッファを走査して img/css/js のURLを先に発見し取得開始───▶

要点は、取得(フェッチ)の前倒しであってDOM構築の前倒しではないことです。スキャナは「何を取りに行くべきか」を早く知るための仕組みであり、実行順序やDOMの意味づけは依然メインパーサが担います。基礎の全体像は ブラウザのレンダリングの仕組み を参照してください。

パーサブロッキング:script と CSS が描画を止める条件

なぜ <script> の位置が決定的なのか。鍵はスクリプトとスタイルの相互依存にあります。

  • 同期 script はパースをブロックする。 <script src>(async / defer なし)はDOM構築を止め、取得と実行を完了させてから次へ進みます。これはスクリプトが document.write で後続のトークン列を書き換えうるため、ブラウザが先のDOMを確定できないからです。
  • script は先行する CSS の完了を待つ。 スクリプトは getComputedStyle などでスタイルを読み取れるため、ブラウザは未完成のCSSOMでスクリプトを走らせるわけにいきません。結果として、ある同期スクリプトの実行は、それより前に現れた <link rel=stylesheet> のCSSOM構築完了を待ちます。
head の同期 script が描画を直列に止める仕組み

head に <link rel=stylesheet> と同期 <script> がこの順で並ぶと、(1) CSS取得→CSSOM構築、(2) その完了を待ってスクリプト実行、(3) ようやくパース再開、と直列になります。スクリプト自体は描画をブロックしますが、その手前でCSSの完了待ちまで挟まるため、体感はさらに悪化します。async / defer を付けるとパースブロッキングが外れます。

指定取得タイミング実行タイミングパースブロック
script(無印・外部)発見後すぐ取得完了後すぐ(DOM到達順)する
script async発見後すぐ取得完了次第(順不同)しない
script defer発見後すぐパース完了後・DOMContentLoaded前(記述順)しない
link rel=stylesheet発見後すぐ—描画をブロック(後続scriptも待たせる)

CSS はレンダリングブロッキングリソースです。スタイルが未確定のまま描画すると、いわゆるスタイルなしコンテンツのちらつき(FOUC)が起きるため、ブラウザは重要なCSSのCSSOM構築が終わるまで最初のペイントを抑制します。async / defer でも、document.write を使わないスクリプトでも、このCSSの描画ブロックは別の話として残ります。

リソースの内部優先度:ブラウザが自動で付ける順位

取得すべきURLが分かっても、回線とサーバーの同時接続には限りがあります。そこでブラウザは各リクエストに内部優先度(resource priority)を割り当て、スケジューラがこの優先度に従って発行順とネットワーク上の重みを決めます。HTTP/2 以降では、これがストリームの重み・依存としてサーバーへ伝わります(詳細は HTTP/2の多重化とHPACKヘッダ圧縮の原理 を参照)。

横にスクロール

リソースの内部優先度:ブラウザが自動で付ける順位について、ブラウザ内部の処理順、状態、権限境界、性能と失敗条件を示した解説図
リソースの内部優先度:ブラウザが自動で付ける順位を題材に、ブラウザ内部の処理順と状態・権限境界を追い、実装判断に必要な観測点を整理します。

優先度はおおむね次の観点から自動決定されます(Chromium の挙動を基準とした概略で、ブラウザ差があります)。

リソース既定の優先度(概略)決め手
HTML(ドキュメント)Highest後続すべての起点だから
CSS(head のstylesheet)Highest描画をブロックするため最優先
同期 script(早い位置)Highパースを止めるため
script async / deferLow描画を妨げないため
フォントHighテキスト描画に直結
ビューポート内の画像Low→Mediumレイアウト確定後に引き上げ
ビューポート外の画像Lowest今は見えないため

ここで効いてくるのが位置とビューポートです。画像はマークアップ発見時点では既定で低優先ですが、レイアウトが進んで初期ビューポート内にあると判明すると、ブラウザは優先度を引き上げ直すことがあります。逆に画面外の画像は低いまま据え置かれます。<head> 内のCSSやフォントが高いのは、それらが描画を直接律速するからです。

プリロードスキャナと優先度は別レイヤー

プリロードスキャナは「いつ発見するか(前倒し)」、内部優先度は「発見したものをどの順で流すか(重み付け)」を担います。スキャナが早く画像を見つけても、その画像が低優先なら、高優先のCSSやフォントの後ろに回されます。両者を分けて理解すると、後述の fetchpriority がどこに効くかが明確になります。

Priority Hints(fetchpriority)の効き方と限界

fetchpriority 属性(high / low / auto)は、ブラウザが自動算出した内部優先度を上書きするヒントです。順序を直接固定するのではなく、スケジューラへの優先度シグナルを変える点が肝心です。

<!-- LCP となるヒーロー画像を最優先で取りに行かせる -->
<img src="hero.webp" fetchpriority="high" width="1200" height="630" alt="主役の写真" />

<!-- 重要度の低い装飾画像やサードパーティ画像を後ろへ下げる -->
<img src="decoration.webp" fetchpriority="low" alt="" />

<!-- preload と併用して、本当に最初に要るものを明示 -->
<link rel="preload" as="image" href="hero.webp" fetchpriority="high" />

最も費用対効果が高い使い所はLCP画像です。前述のとおり画像の既定優先度は低く、ブラウザがビューポート内と判定して引き上げるまでに一拍遅れが出ます。fetchpriority="high" を主役画像に付けると、その判定を待たず初手から高優先で取得を始められ、LCP が縮みます。逆に、ファーストビューに不要なカルーセルの2枚目以降などを low に下げると、重要リソースへ帯域を譲れます。

preload と fetchpriority は役割が違う

<link rel="preload"> は「プリロードスキャナより前に、確実に発見させる」ための先回り宣言です。一方 fetchpriority は「発見済みリソースの優先度を変える」ヒントです。CSS の background-image や JS で動的生成する画像など、スキャナが見つけられないリソースは preload で発見を早め、そこへ fetchpriority="high" を重ねると効果が最大化します。

効かない・逆効果になる典型

fetchpriority="high" を多くの要素に付けると、優先度の差が消えて全体が遅くなります。優先度は相対的な順位付けなので、「全部 high」は「全部 normal」と同義です。また fetchpriority はあくまでヒントで、<head> のCSSやフォントといった本来高優先のリソースを押しのけて画像を最優先化することは保証されません。レンダリングブロッキングの解消(async / defer、クリティカルCSSの分離)が先で、fetchpriority はその上の微調整です。

実務での適用指針

押さえどころ

出題・実務で問われるのは、(1) プリロードスキャナが「取得の前倒し」であってDOM構築の前倒しではない点、(2) 同期 script がパースをブロックし、かつ先行CSSのCSSOM完了を待つ理由(document.write とスタイル読み取り)、(3) 内部優先度が要素種別・位置・ビューポートで自動決定される点、(4) fetchpriority がその優先度を上書きする「ヒント」であり、相対順位なので濫用すると無効化する点、の4つです。

  • クリティカルパスを直列にしない:head の同期 script を避け、defer(記述順を保つ)か async(順不同で良いもの)へ。CSS は必要分を小さく保ち、描画ブロック時間を短くする。
  • LCP画像を前倒し:主役画像に fetchpriority="high" を付け、必要なら preload で発見も早める。ファーストビュー画像に loading="lazy" は付けない。
  • スキャナの死角を埋める:CSS背景やJS生成のリソースはスキャナに見えないため、preload で明示する。
  • 優先度は相対:high は本当に効かせたい1〜2個に絞り、不要なものを low に下げて差を作る。

まとめ

まとめ

ブラウザはメインパーサでDOMを直列に組みつつ、プリロードスキャナで先のトークンを走査して画像・CSS・JSの取得を前倒しします。同期 script はパースをブロックし、先行CSSのCSSOM完了まで待つため、head の重い script は描画を直列に止めます。取得すべきURLが決まると、ブラウザは要素種別・位置・ビューポートから内部優先度を自動付与し、fetchpriority はその値を上書きするヒントとして働きます。特に低優先になりがちなLCP画像へ high を与えると表示が縮みます。仕上げに レンダリングパイプライン詳説 と Web パフォーマンス を合わせると、取得から描画までの律速点を一本の線で説明できます。

Web/フロントエンドの記事ガイド

ブラウザの投機的パース処理とリソース優先度を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

ブラウザ

比較で見る軸

難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5

導入後に効く点

同期 script はパースをブロックし、後続の script は先行する CSSOM 構築の完了を待つ。だから head の重い script は描画を直列に止める。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
Web/フロントエンド
タグ数
5

判断チェックリスト

  • 自社の用途が「ブラウザ / パフォーマンス」に近いか確認する。
  • 強みである「HTMLパーサ本体が同期 script で止まっている間も、プリロードスキャナが先のトークンを走査して img・CSS・script などのURLを先行ダウンロードする。これが初期表示の取得を前倒しする中核。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

ブラウザパフォーマンスHTMLリソース読み込みCore Web Vitals
参考: 公式情報