HeadとFooterへのJavaScript配置:Core Web Vitalsへの影響
head内でのdeferが現在のベストプラクティスである理由と、footerへの配置が今でも理にかなっているケース
端的な回答: head内でdeferを使う
最終査読: Arjen Karel (2026年3月)
JavaScriptをフッターに配置するというのがかつての定石でした。そのアドバイスはもはや時代遅れです。すべてのブラウザがasyncとdeferをサポートしている現在、ほとんどのスクリプトは<head>内にdefer属性を付けて配置するのが最適です。
理由はpreload scannerにあります。ブラウザはhead内のスクリプトを即座に検出し、HTMLのパースと並行してダウンロードを開始します。フッターのスクリプトは後で検出されるため、ダウンロードの開始も遅れます。結果として同じノンブロッキングの動作になりますが、リソースの検出は前者のほうが速くなります。
2025 Web Almanacによると、85%のページが依然としてrender blockingなリソースの監査に不合格となっています。これは膨大な数です。その一方で、モバイルのTotal Blocking Timeは前年比で58%増加し、中央値は1,916ミリ秒に達しました。JavaScriptは軽くなるどころか、ますます重くなっています。スクリプトの配置を正しく行うことは、First Contentful PaintとLargest Contentful Paintを改善するための最も簡単な方法の1つです。
preload scannerの仕組み
preload scannerは、メインのパーサーより先行して動作する2つ目のHTMLパーサーです。生(raw)のHTMLを素早くスキャンし、メインパーサーが到達する前に重要なリソース(画像、CSS、JavaScript)のフェッチを開始します。preload scannerは、ほぼ検出した順序でリソースをフェッチします。
ここでスクリプトの配置が重要になります。<head>内のスクリプトはほぼ即座に検出されます。<body>の末尾にあるスクリプトは、特に大きなHTMLドキュメントでは、後になってから検出されます。この遅れは、モバイル回線において数百ミリ秒のロスにつながります。
動的に挿入されたスクリプト(JavaScriptで生成されたもの)は、preload scannerからはまったく見えません。スキャナーが検出するのは、サーバーが返すHTMLマークアップ内に存在するリソースのみです。コードでスクリプトを挿入するよりも、HTML属性を使ってJavaScriptを遅延させるほうがほぼ常に優れているのはこのためです。
head内のJavaScript
ページの<head>内にJavaScriptを配置すると、preload scannerによる検出が最も早くなります。
メリット
- 早期の検出: preload scannerは、bodyコンテンツより先にheadスクリプトを見つけます。ダウンロードは可能な限り早く開始されます。
- より早い実行: head内のスクリプト(
defer付き)は、ドキュメントの後半で検出されるスクリプトよりも先に実行されます。詳細な比較については、deferとasync JavaScript、そしてCore Web Vitalsを参照してください。 - コードの分離: スクリプトの参照を
<head>内にまとめることで、コンテンツのマークアップと分離され、HTMLの保守が容易になります。
デメリット
- Render blocking(deferやasyncなしの場合): head内に通常の
<script>を記述すると、スクリプトのダウンロードと実行が終わるまでHTMLのパースがブロックされます。これはFirst Contentful Paintに致命的な影響を与えます。これを回避するため、head内の外部スクリプトには必ずdeferまたはasyncを使用してください。 - 帯域幅の競合: 優先度の高いheadスクリプトは、CSS、フォント、LCP画像と帯域幅を競合します。低速な回線では、これによりLargest Contentful Paintが2.5秒の基準値を超えてしまう可能性があります。
headへの配置を使うべきケース
ページ体験に不可欠なスクリプトには<head>を使用してください。メニュー、Cookieの通知、スライダー、または訪問者がファーストビューで見るものに影響するスクリプトなどです。defer(実行順序が問われない場合はasync)を追加し、render blockingを防いでください。機能検出(feature detection)ライブラリも、bodyのパース前に実行する必要があるため、head内に配置します。
フッターのJavaScript
JavaScriptを</body>の閉じタグの直前に配置するのは、長年にわたるパフォーマンス改善の定石でした。その考え方は、HTMLのレンダリングを先に行い、スクリプトのダウンロードを後回しにするというものです。
メリット
- デフォルトでノンブロッキング: フッターのスクリプトは、それより上のHTMLがすでにパースされているため、初期レンダリングをブロックしません。
- 帯域幅の競合が少ない: ブラウザがフッターのスクリプトに到達する頃には、CSS、フォント、LCP画像のダウンロードはすでに開始されています。
デメリット
- 検出が遅い: preload scannerは、headスクリプトよりも遅れてフッタースクリプトを見つけます。大きなページでは、ダウンロードの開始も実行も遅れることになります。
- 時代遅れのパターン:
<head>内の<script defer>は、より早い検出と同時にノンブロッキングな動作を実現します。フッターへの配置は、すべてのブラウザがdeferをサポートする前の回避策でした。その時代はすでに終わっています。
フッターへの配置が依然として有効なケース
初期のページロード時に帯域幅の競合を絶対に避けたいスクリプト(アナリティクス、A/Bテストツール、ソーシャルウィジェットなど)であれば、フッターへの配置が理にかなう場合があります。しかし、そうしたスクリプトであっても、preload scannerによる検出が早まるため、通常はhead内でdeferを使う方が賢明です。
モダンなスクリプト属性
asyncとdeferのほかに、知っておくべき属性があと2つあります。
type="module": モジュールスクリプトはデフォルトで遅延(defer)されます。すでにそのように動作するため、モジュールスクリプトにdeferを追加する必要はありません。モジュールスクリプトにasyncを追加すると、デフォルトのdeferの挙動が上書きされ、ダウンロード完了後すぐに実行されます。
fetchpriority: デフォルトでは、asyncとdeferスクリプトにはLowのネットワーク優先度が割り当てられます。asyncスクリプトにfetchpriority="high"を追加すると、Highの優先度でノンブロッキングなダウンロードが行われます。これは、UXに不可欠でありながらレンダリングをブロックしてはならないスクリプトにとって理想的な組み合わせです。全体像については、リソースの優先度設定ガイドとJavaScriptの優先度レベルを参照してください。
実用的なスクリプト配置の戦略
すべてのスクリプトを同じように扱うべきではありません。私は4つの階層に分けるアプローチを採用しています。
- レンダリングに不可欠なスクリプト: ページの見た目のレイアウト(メニュー、スライダー、ファーストビューのUI)に影響を与えるスクリプトです。first paint前に実行する必要がある場合は、
deferなしで<head>内に配置します。これらはrender blockingを引き起こし、Core Web Vitalsを悪化させるため、できる限り小さく抑えてください。 - 重要なスクリプト: コンバージョンやインタラクション(フォーム、ナビゲーション、Cookieの同意)に不可欠なスクリプトです。
deferまたはasyncを付けて<head>内に配置します。 - 通常のスクリプト: 最初のページレンダリングに影響を与えないスクリプト(カルーセル、モーダル、タブ)です。
deferを付けて<head>内に配置します。 - あると便利なスクリプト: どうしても必要な場合を除き、なくても構わないスクリプト(アナリティクス、チャットウィジェット、ソーシャルシェア)です。
loadイベントやrequestIdleCallbackを使用して、ページのレンダリング完了後にこれらを読み込みます。テクニックについては、JavaScriptを遅延させる16の方法を参照してください。このカテゴリの未使用のJavaScriptが多数ある場合は、未使用のJavaScriptを削減する方法を参照してください。
DOMContentLoadedおよびloadイベントを使用すると、HTML内のスクリプトの配置場所に関係なく、実行タイミングを制御できます。これは、コードを適切なタイミングで確実に実行させるために役立ちます。
コード例
例1: head内のJavaScript(render blocking)
<!DOCTYPE html>
<html>
<head>
<title>JavaScript in the Head Example</title>
<script>
function showMessage() {
alert("Hello from JavaScript in the head!");
}
</script>
<!-- This script blocks rendering -->
<script src="script.js"></script>
</head>
<body>
<button onclick="showMessage()">Click Me</button>
</body>
</html>
この例では、<head>内のscript.jsがレンダリングをブロックしています。スクリプトがダウンロードされ、実行されるまで、ブラウザはボタンを表示しません。これを修正するには、scriptタグにdeferを追加します。
例2: フッターのJavaScript
<!DOCTYPE html>
<html>
<head>
<title>JavaScript in the Footer Example</title>
</head>
<body>
<button onclick="showMessage()">Click Me</button>
<!-- Script at the end of the body -->
<script src="script.js"></script>
<script>
function showMessage() {
alert("Hello from JavaScript in the footer!");
}
</script>
</body>
</html>
この場合、script.jsはHTMLコンテンツの後に読み込まれるため、レンダリングをブロックしません。しかし、preload scannerによる検出は、headにある場合よりも遅れます。<head>内で<script defer src="script.js"></script>を使用すれば、同じノンブロッキングの動作を保ちつつ、ダウンロードの検出を早めることができます。
例3: イベントリスナーの使用
<!DOCTYPE html>
<html>
<head>
<title>Event Listener Example</title>
<script>
window.addEventListener('load', function() {
console.log("Page is fully loaded.");
});
</script>
</head>
<body>
<!-- Page content -->
</body>
</html>
loadイベントリスナーは、スクリプトの配置場所に関わらず、ページが完全に読み込まれた後にのみコールバック内のコードを実行させます。これは、他のすべての準備が整うまで待つべき、重要ではない初期化処理に役立ちます。
変更の検証
スクリプトをフッターから<head>内のdeferへ移動した後は、Real User Monitoringでその効果を検証してください。FCPとLCPの両方が改善するはずです。CoreDashが監視しているサイト全体で見ると、大多数のスクリプトにdeferを使用しているオリジンは、依然としてフッターへの配置に依存しているオリジンと比較して、FCPの中央値が18%高速です。
何が本当に遅いのか、見つけ出します。
フィールドデータでCritical Rendering Pathをマッピング。Lighthouseレポートではなく、優先順位付きの修正リストをお渡しします。
監査を依頼する