タイトル: scriptタグの記載場所
SEOタイトル: HTML script タグ配置完全ガイド (defer/async)
| この記事の要点 |
|
結論: 現代のベストプラクティス
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>例</title>
<!-- ✅ 自前 JS: head 内に defer で -->
<script src="/js/main.js" defer></script>
<!-- ✅ ES Modules は自動で defer -->
<script src="/js/app.mjs" type="module"></script>
<!-- ✅ サードパーティ計測 (依存関係なし): async -->
<script src="https://www.googletagmanager.com/gtag/js?id=G-XXX" async></script>
</head>
<body>
<h1>コンテンツ</h1>
</body>
</html>
script タグの 3 つの読み込みモード
| モード | HTML パース | 実行タイミング | 順序保証 |
|---|---|---|---|
<script> (素) | ブロック | ダウンロード完了次第即実行 | ○ |
<script defer> | ブロックしない | HTML パース完了後、DOMContentLoaded 前 | ○ |
<script async> | ブロックしない | ダウンロード完了次第即実行 | × |
<script type="module"> | ブロックしない | defer と同等 | ○ |
歴史: なぜ 末尾に置くテクニックが流行ったか
2000 年代後半〜 2010 年代前半、まだ defer/async が普及していなかった頃、<script> を <head> に置くと:
- ブラウザは HTML パース中に script タグに遭遇
- JS ファイルのダウンロード・実行が完了するまでHTML パースを停止
- 結果、ユーザーには白画面が長く表示される
これを避けるため、「</body> の直前に置けば HTML パースは終わってる」というテクニックが広まりました。jQuery 全盛期の Web 制作本はほぼこのスタイルです。
<!-- 旧来のスタイル (jQuery 時代) -->
<!DOCTYPE html>
<html>
<head>
<title>例</title>
</head>
<body>
<h1>コンテンツ</h1>
<!-- ページ最後に置くことでパースブロックを回避 -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/js/main.js"></script>
</body>
</html>
defer 属性 (推奨)
defer を付けると、ブラウザは:
- script を並行ダウンロード (HTML パースをブロックしない)
- HTML パース完了後に、記述順に実行
DOMContentLoadedイベントの直前に実行される
<head>
<!-- 順序が保持される -->
<script src="/js/jquery.js" defer></script> <!-- 1番目に実行 -->
<script src="/js/plugin.js" defer></script> <!-- 2番目に実行 -->
<script src="/js/main.js" defer"></script> <!-- 3番目に実行 -->
</head>
async 属性
async も並行ダウンロードしますが、ダウンロードが完了した順に即実行します。順序は保証されません。
<head>
<!-- 順序不定 (依存関係があるとバグる) -->
<script src="/js/jquery.js" async></script>
<script src="/js/plugin.js" async></script> <!-- jquery より先に走ることがある -->
</head>
<!-- ✅ 単独で動く計測タグや広告タグには async が最適 -->
<script src="https://www.googletagmanager.com/gtag/js?id=G-XXX" async></script>
<script src="https://connect.facebook.net/en_US/sdk.js" async></script>
type="module" (ES Modules)
ES Modules (import/export を使うモジュール) は常に defer 扱いです。明示的に defer を書かなくても OK:
<script type="module" src="/js/app.mjs"></script>
<!-- defer 相当 + strict mode + import/export 使える -->
<!-- インラインモジュール -->
<script type="module">
import { greet } from '/js/lib.mjs';
greet('world');
</script>
<!-- 古いブラウザ用のフォールバック -->
<script nomodule src="/js/legacy-bundle.js"></script>
Critical CSS との関係 (Web Vitals)
Core Web Vitals の LCP (Largest Contentful Paint) 改善には、ファーストビュー描画を阻害しないことが重要です。defer / async の徹底に加え、Critical CSS のインライン化を組み合わせます:
<head>
<!-- 1. Critical CSS をインライン (ファーストビュー描画用) -->
<style>
/* hero / nav の最低限のスタイル */
body { margin: 0; font-family: sans-serif; }
.hero { height: 60vh; background: #4a90e2; }
</style>
<!-- 2. 残りの CSS は非同期に -->
<link rel="preload" href="/css/main.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
<!-- 3. JS は defer -->
<script src="/js/app.js" defer></script>
<!-- 4. 計測タグは async -->
<script src="/gtag.js" async></script>
</head>
Resource Hints (preload/prefetch/preconnect)
ヒントを使うとさらに高速化できます:
<head>
<!-- 重要 JS を早期ダウンロード開始 -->
<link rel="preload" href="/js/critical.js" as="script">
<!-- 次ページで使うリソースを先読み -->
<link rel="prefetch" href="/js/next-page.js">
<!-- 接続だけ先に確立 -->
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
<script src="/js/critical.js" defer></script>
</head>
サードパーティ JS の遅延読込
チャットウィジェット・コメント欄・SNS シェアボタン等、ファーストビューに不要な JS は意図的に遅延させると Web Vitals が上がります:
// ユーザー操作後にチャットウィジェットを読み込む
document.addEventListener('scroll', loadChat, { once: true });
document.addEventListener('click', loadChat, { once: true });
function loadChat() {
const s = document.createElement('script');
s.src = 'https://widget.example.com/chat.js';
s.async = true;
document.head.appendChild(s);
}
// または IntersectionObserver で要素が見えたとき
const obs = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
const s = document.createElement('script');
s.src = '/js/comments.js';
document.head.appendChild(s);
obs.disconnect();
}
});
obs.observe(document.querySelector('#comments-section'));
使い分け早見表
| JS の種類 | 推奨配置 | 属性 |
|---|---|---|
| 自前のアプリケーション JS | head 内 | defer |
| ES Modules | head 内 | type="module" |
| GA / GTM / Facebook Pixel | head 内 | async |
| jQuery + プラグイン | head 内 | defer (順序保持) |
| インライン script (極小) | head 内 or body 内 | 属性不要 |
| レガシーブラウザ対応 (IE11) | body 末尾 | 属性なし |
FAQ
Q: defer と async どちらが速い?
A: ダウンロード速度は同じ。実行タイミングが違うだけです。順序が必要なら defer、不要なら async。
Q: インライン に defer は効く?
A: 効きません。defer/async は外部ファイル (src 指定) にのみ有効です。
Q: body 末尾でも defer は必要?
A: body 末尾なら不要ですが、現代では head + defer に統一すると保守しやすいです。