連載「実例に学ぶサイトスピード改善」
実在サイトの表示速度ボトルネックを取り上げるシリーズ。原因と対処法をシミュレーションの数値で示しながら解説する。事例の詳細は弊社の表示速度ボトルネック実例研究(vigilante)で公開している。
ブラウザはscriptタグの前で立ち止まる
ブラウザはHTMLを上から順に読みながら、次に取得すべきリソースを判断していく。その途中で async / defer 属性のない <script> タグに出会うと、処理が止まる。スクリプトのダウンロードと実行が終わるまで、HTMLの解析は先へ進まない。これが同期スクリプトによるレンダリングブロックの正体だ。
外部ドメインからスクリプトを読み込んでいる場合は、DNSの解決とTLS接続の確立にかかる時間が上乗せされる。その間もHTMLのパースは止まったままになる。
弊社の表示速度ボトルネック実例研究(vigilante)では、この問題を国内サイトで繰り返し観測している。影響の形はさまざまで、FCP(First Contentful Paint)への直接的な遅延から、HTML本体の肥大化、ページ全体の安定性低下まで幅がある。4つの事例とともに確認していく。

ブラウザがscriptに出会うと何が起きるか
ブラウザはHTMLのパース中に <script src="..."> タグを見つけると、次の順で処理する。
- HTMLのパースを停止する
- スクリプトをダウンロードする(外部ファイルの場合)
- スクリプトを実行する
- 実行が完了したらHTMLのパースを再開する
<head> 内に置かれた同期スクリプトの場合、CSSの読み込みよりも早い段階でこれが起きる。つまりスタイルシートのダウンロードとレンダリングツリーの構築が、スクリプトの完了を待つことになる。
async 属性を付ければ、HTMLのパースと並行してスクリプトをダウンロードできる(実行はダウンロード完了次第で順序は不定)。defer 属性を付ければ、パースを妨げずにダウンロードし、パース完了後に記述順で実行される。どちらの属性もない状態が「同期」で、これがボトルネックになりやすい。
外部ドメインからの読み込みでは、この問題がより深刻になる。DNS解決とTLS接続の確立が済むまで、スクリプトのダウンロードそのものが始まらないからだ。接続コストが丸ごとHTMLパースのブロック時間に変わる。
実例で確認する同期スクリプトの影響
じゃらんニュース:外部CDN同期jQueryがFCPを1.3秒押し下げた(シミュレーション)
リクルートが運営する旅行・おでかけ情報メディアじゃらんニュースでは、<head> 内の最初の <script> タグで ajax.googleapis.com からjQuery 1.8.3が同期読み込みされていた。外部ドメインへのリクエストではDNS解決とTLS接続の確立が必要で、この待機は約300〜500msにのぼる。その間、HTMLのパースは止まったままだ。同じく maxcdn.bootstrapcdn.com からFont Awesome CSSもレンダリングブロックリソースとして読み込まれていた。
解消シミュレーションでは、これらの外部CDNリソースを同一ドメイン(www.jalan.net)からの配信に切り替えた。外部ドメインへの接続コストが排除され、既存のHTTP接続が再利用される形になる。
| 指標 | 解消前 | 解消後(シミュレーション) | 変化量 |
|---|---|---|---|
FCP | 2.0秒 | 0.7秒 | -1.3秒 |
LCP | 2.2秒 | 0.9秒 | -1.3秒 |
SI | 2.8秒 | 2.1秒 | -0.7秒 |
総合スコア | 97 | 100 | +3 |
シミュレーションでは FCP が2.0秒から0.7秒へ1.3秒短縮された。LCP も同じく1.3秒短縮されている。head内の最初のスクリプトが外部ドメインへの同期リクエストを発していたことで、ページ全体の描画開始が押し下げられていた構図が読み取れる。
同サイトにはさらにhead内に15個の同期スクリプトが残っていた。これらをbody末尾に移動したシミュレーションでは、FCP がさらに0.1秒短縮されている。複数の同期スクリプトが積み重なるほど遅延が累積するパターンだ。
詳細はじゃらんニュースのボトルネック研究で確認できる。
朝日新聞デジタル:head内3件の同期スクリプトがレンダリングをブロック
朝日新聞デジタルは観測時点で 総合スコア 96、LCP 0.3秒と、基礎的なパフォーマンスが高水準にある。それでもhead内に async / defer 属性のない同期スクリプトが3件存在していた(jquery-1.9.0.min.js、sp_top.js、ad/js/sp/top.js)。CSSのパース完了後にこれらが直列で実行され、合計約23msのレンダリングブロックが発生していた。
解消シミュレーションでは3件のスクリプトをbody末尾に移動した。
| 指標 | 解消前 | 解消後(シミュレーション) | 変化量 |
|---|---|---|---|
FCP | 0.3秒 | 0.2秒 | -0.1秒 |
SI | 1.5秒 | 1.4秒 | -0.1秒 |
シミュレーションでは FCP が0.3秒から0.2秒へ短縮された。元の値がすでに小さいため変化幅は0.1秒にとどまるが、レンダリングブロックの排除によってHTML解析が先行できるようになった効果が確認できる。
この結果が示すのは、フロントエンドの実装品質が高い状態でも、同期スクリプトのレンダリングブロックは残りうるという点だ。特別なサイトの話ではなく、広く見られるパターンだろう。詳細は朝日新聞デジタルのボトルネック研究を参照してほしい。
Hamee本店:1MBの巨大インラインスクリプトでHTMLが肥大化
同期スクリプトの問題は外部ファイルだけではない。HTMLに直接書き込まれたインラインスクリプトも、パース負荷の観点でボトルネックになりうる。
スマートフォンケース・アクセサリーを扱うHamee本店(strapya.com)では、インラインスクリプトとして、サイト内検索用の全商品データJSON(約703KB)とフォームビルダー定義データJSON(約303KB)がHTMLに埋め込まれていた。合計約1MBのデータがHTMLの一部として配信される構成で、HTMLサイズは約1.94MBに達していた。
いずれも初期表示には不要なデータだ。ページを表示する前にHTMLをまるごとダウンロードしパースするコストが、そのまま表示速度に乗る。
解消シミュレーションでは初期表示に不要な2つの巨大インラインスクリプトを削除した。HTMLサイズは1.94MBから1.00MBへ約50%削減された。
| 指標 | 解消前 | 解消後(シミュレーション) | 変化量 |
|---|---|---|---|
LCP | 1.2秒 | 1.0秒 | -0.2秒 |
シミュレーションでは LCP が0.2秒短縮された。HTMLの転送量が削減されたことで、ドキュメントのダウンロード完了が早まり、後続リソースの取得開始が前倒しになった効果だと考えられる。FCP自体への影響はこのシミュレーションでは観測されなかったが、HTMLの肥大化がLCPに対してボトルネックになっていたことは数値から確認できる。
詳細はHamee本店のボトルネック研究を参照してほしい。
高島屋オンラインストア:未依存スクリプトのasync化がページ安定性を改善(シミュレーション)
高島屋オンラインストアでは、body末尾に配置された3つのスクリプト(footerFixed.js、Chart.min.js、smartphoto.js)が async 属性なしで同期的に読み込まれていた。いずれも他のスクリプトから依存されていないことを静的解析で確かめ、async 属性を付与するシミュレーションを実施した。
<!-- 変更後 -->
<script src="/sto/common/js/footerFixed.js" async></script>
<script src="/cdn/cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.4/Chart.min.js" async></script>
<script src="/sto/common/js/lib/smartphoto.js" async></script>| 指標 | 解消前 | 解消後(シミュレーション) | 変化量 |
|---|---|---|---|
CLS | 0.383 | 0.209 | -0.174 |
総合スコア | 82 | 89 | +7 |
シミュレーションではFCP・LCPへの直接的な影響は観測されなかったが、CLS(Cumulative Layout Shift = レイアウトのずれ)が0.383から0.209へ大きく変化し、総合スコア が7ポイント向上した。async 属性によってスクリプトの実行順序が変わり、レイアウトシフトのタイミングに影響を与えたと考えられる。
同期スクリプトの非同期化がFCPだけでなく、ページの安定性にも波及しうることを示す例だ。詳細は高島屋オンラインストアのボトルネック研究にまとめている。
同期スクリプトを非同期化する対処法
defer属性:パース完了後に順序を守って実行
<!-- 同期(問題のある状態) -->
<script src="app.js"></script>
<!-- deferに変更 -->
<script src="app.js" defer></script>defer を付けると、スクリプトのダウンロードをHTMLパースと並行して行い、HTMLのパース完了後に記述順で実行する。他のスクリプトに依存しているスクリプト(実行順序が重要)には defer が適している。
async属性:ダウンロード完了次第実行
<script src="analytics.js" async></script>async を付けると、ダウンロード完了次第に実行される。実行順序は保証されない。解析ツールや広告タグなど、他のスクリプトとの依存関係がないスクリプトに適している。
head内スクリプトのbody末尾移動
defer / async の付与が難しい場合でも、<head> 内のスクリプトを </body> 直前に移動するだけでレンダリングブロックを回避できる。
<!-- 変更前: headに同期スクリプト -->
<head>
<script src="heavy.js"></script>
</head>
<body>
<!-- コンテンツ -->
</body>
<!-- 変更後: body末尾に移動 -->
<head>
<!-- スクリプトなし -->
</head>
<body>
<!-- コンテンツ -->
<script src="heavy.js"></script>
</body>HTMLのパースを先行させることができ、FCPの改善につながりやすい。
インラインスクリプトの肥大化を防ぐ
Hamee本店の例のように、全商品データや設定データをHTMLに丸ごと埋め込む構成は、初期表示後に非同期で取得する形に切り替えることで、HTMLの転送サイズを大幅に削減できる。
<!-- 問題: 初期表示に不要な大量データがinlineで埋め込まれている -->
<script>
window.ALL_PRODUCTS = [/* 703KB分のJSON */];
window.FORM_CONFIG = [/* 303KB分のJSON */];
</script>
<!-- 解決: 必要なタイミングでAPIから非同期取得する -->
<script>
// 検索が必要になったときに初めてデータを取得する
async function initSearch() {
const res = await fetch('/api/products.json')
window.ALL_PRODUCTS = await res.json()
}
</script>確認方法
手元のサイトで同期スクリプトの問題を確認するには、以下の方法が手軽だ。
Chrome DevToolsのPerformanceタブを開いてページをリロードすると、タイムラインが表示される。Parse HTML の処理中に Evaluate Script が割り込んでいる箇所が、同期スクリプトによるブロックにあたる。
Lighthouseレポートでは、「Eliminate render-blocking resources」の指摘に <script> タグが含まれていれば、それが対象だ。
まとめ
- head内の同期スクリプトはHTMLパースを止める。
async/defer属性のない<script>タグは、ダウンロードと実行が終わるまでパースを中断させる。この遅延はFCPに直接影響する。 - 外部CDNからの同期読み込みは遅延が複合する。 じゃらんニュースの例では、外部CDN(ajax.googleapis.com)から同期読み込みされたjQueryがDNS解決とTLS接続の待機をもたらしており、同一ドメイン化でFCPが2.0秒から0.7秒へ1.3秒改善するというシミュレーション結果になった。
- インラインスクリプトの肥大化もHTMLパース負荷を高める。 Hamee本店では初期表示に不要な約1MBのJSONがHTML本体に埋め込まれ、HTMLサイズが1.94MBに達していた。削除だけでLCPが0.2秒改善するというシミュレーション結果が得られた。
- async化の効果はFCPだけではない。 高島屋オンラインストアの例では、未依存スクリプトへのasync付与がCLSの大幅な改善(0.383→0.209)につながり、総合スコアが7ポイント向上するというシミュレーション結果になった。
- 対処は属性の付与か位置の変更が基本。
defer/asyncの付与、またはhead内からbody末尾への移動で対応できる。インラインスクリプトは不要なデータを取り除くか、非同期取得に切り替える。 - 実装品質が高いサイトでも同じ問題は起きる。 朝日新聞デジタルのように総合スコア96の状態でも、head内に同期スクリプトが残っていた。特別なサイトの話ではなく、広く見られるパターンだろう。
自社サイトの同期スクリプトや他のボトルネックを体系的に調べたい場合は、弊社の表示速度ボトルネック研究 vigilante の事例が参考になる。より詳しい調査が必要な場合はアイデアマンズまでご相談いただきたい。