タイトル: scriptタグの記載場所
SEOタイトル: HTML script タグ配置完全ガイド (defer/async)
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><code><script></code> の配置は <strong><head> 内 + defer 属性</strong>が現代のベストプラクティス</li><li>昔は <strong><body> の閉じタグ直前</strong>に置くテクニックが主流だった (HTML パースをブロックしないため)</li><li><strong>defer</strong>: HTML パース完了後に実行、複数 script の順序保持</li><li><strong>async</strong>: ダウンロード完了次第すぐ実行、順序保証なし、依存関係のない計測タグ向き</li><li><strong>type="module"</strong> は自動で defer 扱い。最新の ES Modules はこれで OK</li></ul></td></tr></table>
<h2>結論: 現代のベストプラクティス</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!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></code></pre>
<h2>script タグの 3 つの読み込みモード</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>モード</th><th>HTML パース</th><th>実行タイミング</th><th>順序保証</th></tr></thead>
<tbody>
<tr><td><code><script></code> (素)</td><td><strong>ブロック</strong></td><td>ダウンロード完了次第即実行</td><td>○</td></tr>
<tr><td><code><script defer></code></td><td>ブロックしない</td><td>HTML パース完了後、DOMContentLoaded 前</td><td><strong>○</strong></td></tr>
<tr><td><code><script async></code></td><td>ブロックしない</td><td>ダウンロード完了次第即実行</td><td>×</td></tr>
<tr><td><code><script type="module"></code></td><td>ブロックしない</td><td>defer と同等</td><td>○</td></tr>
</tbody>
</table>
<h2>歴史: なぜ <body> 末尾に置くテクニックが流行ったか</h2>
<p>2000 年代後半〜 2010 年代前半、まだ <code>defer</code>/<code>async</code> が普及していなかった頃、<code><script></code> を <code><head></code> に置くと:</p>
<ol>
<li>ブラウザは HTML パース中に script タグに遭遇</li>
<li>JS ファイルのダウンロード・実行が完了するまで<strong>HTML パースを停止</strong></li>
<li>結果、ユーザーには白画面が長く表示される</li>
</ol>
<p>これを避けるため、「<code></body></code> の直前に置けば HTML パースは終わってる」というテクニックが広まりました。jQuery 全盛期の Web 制作本はほぼこのスタイルです。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- 旧来のスタイル (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></code></pre>
<h2>defer 属性 (推奨)</h2>
<p><code>defer</code> を付けると、ブラウザは:</p>
<ol>
<li>script を<strong>並行ダウンロード</strong> (HTML パースをブロックしない)</li>
<li>HTML パース完了後に、<strong>記述順に</strong>実行</li>
<li><code>DOMContentLoaded</code> イベントの直前に実行される</li>
</ol>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><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></code></pre>
<h2>async 属性</h2>
<p><code>async</code> も並行ダウンロードしますが、<strong>ダウンロードが完了した順に即実行</strong>します。順序は保証されません。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><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></code></pre>
<h2>type="module" (ES Modules)</h2>
<p>ES Modules (import/export を使うモジュール) は<strong>常に defer 扱い</strong>です。明示的に defer を書かなくても OK:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><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></code></pre>
<h2>Critical CSS との関係 (Web Vitals)</h2>
<p>Core Web Vitals の <strong>LCP (Largest Contentful Paint)</strong> 改善には、ファーストビュー描画を阻害しないことが重要です。defer / async の徹底に加え、Critical CSS のインライン化を組み合わせます:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><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></code></pre>
<h2>Resource Hints (preload/prefetch/preconnect)</h2>
<p>ヒントを使うとさらに高速化できます:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><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></code></pre>
<h2>サードパーティ JS の遅延読込</h2>
<p>チャットウィジェット・コメント欄・SNS シェアボタン等、ファーストビューに不要な JS は意図的に遅延させると Web Vitals が上がります:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// ユーザー操作後にチャットウィジェットを読み込む
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'));</code></pre>
<h2>使い分け早見表</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e8f5e9"><th>JS の種類</th><th>推奨配置</th><th>属性</th></tr></thead>
<tbody>
<tr><td>自前のアプリケーション JS</td><td>head 内</td><td><code>defer</code></td></tr>
<tr><td>ES Modules</td><td>head 内</td><td><code>type="module"</code></td></tr>
<tr><td>GA / GTM / Facebook Pixel</td><td>head 内</td><td><code>async</code></td></tr>
<tr><td>jQuery + プラグイン</td><td>head 内</td><td><code>defer</code> (順序保持)</td></tr>
<tr><td>インライン script (極小)</td><td>head 内 or body 内</td><td>属性不要</td></tr>
<tr><td>レガシーブラウザ対応 (IE11)</td><td>body 末尾</td><td>属性なし</td></tr>
</tbody>
</table>
<h2>FAQ</h2>
<p><strong>Q: defer と async どちらが速い?</strong><br />
A: ダウンロード速度は同じ。実行タイミングが違うだけです。順序が必要なら defer、不要なら async。</p>
<p><strong>Q: インライン <script>...</script> に defer は効く?</strong><br />
A: 効きません。defer/async は外部ファイル (src 指定) にのみ有効です。</p>
<p><strong>Q: body 末尾でも defer は必要?</strong><br />
A: body 末尾なら不要ですが、現代では head + defer に統一すると保守しやすいです。</p>