script要素の新常識・安易にdefer/async属性を付けてはいけない


script要素の位置とasync/defer

script要素はbodyの末尾に記述するのが定説ですが、あるサイトについてscript要素の記述位置やasync/deferが及ぼすパフォーマンスへの影響を実験しました。


  • 外部JavaScript 11ファイル・250kb

  • 因子1 head要素内・body要素の末尾を比較

  • 因子2 deferasync・それらの属性なしを比較

  • いずれのケースもスクリプトエラーがなく期待通りに動作


  • PageSpeed Insightsのモバイルスコアで評価

※ この記事ではscript要素はsrc属性で外部参照することを前提にします。インラインスクリプトではありません。

※ また、1サイトでの結果に過ぎないのでその点も考慮ください。


最速はasync付きでhead要素に記述

PageSpeed Insightsのスコアはばらつきが出るので、10回の計測を行い、上下2個は外れ値として除外中央6個の平均(ExcelのTRIMMEAN関数)を計算した結果がこちらです。



属性なし
async
defer


script要素をhead内に記述
19 😢

63 😁
36


script要素をbody末尾に記述
45
29
29

async要素はほぼすべてのブラウザが対応済み。今後はhead要素にasync付きで記述する」を基準にしていきたいと思います。

<!-- 従来の外部参照script要素の書き方 -->

<body>
...省略...
<script src="/path/to/script.js"></script>
</body>

<!-- これからの外部参照script要素の書き方 -->

<head>
...省略...
<script src="/path/to/script.js" async></script>
...省略...
</head>


安易なasync/deferは逆効果になることも

属性なしでページの上部にscriptを記述するのは、出だしからレンダリングをがっちりブロックするのでいちばんの悪手です。これは昔からの常識です。

body末尾のscript要素にdeferasyncを追加するとかえってパフォーマンスが悪化しました。これは意外な結果でした。

body末尾に記述するということ自体が、レンダリングブロックを避ける擬似的なdeferなので、むやみに遅らせてもLong taskの終点をただ先送りするだけなのかなと推測しています。

今回のケースではdefer自体、あまりいいところがない結果になりました。


asyncの利用は計画的に

asyncは強力ですが、むやみに付けるとスクリプトが期待通りに動作しないことがあります。また、読み込み序盤で必要なスクリプトをasyncにするのも逆効果です。

以下の点を注意しながら使っています。うろおぼえなので別の機会にプラクティスをまとめてみたいところです。


  • インラインスクリプトにはasyncが使えないので注意。loadイベントにくるむなど実行遅延が必要かも。

  • DOMContentLoadedイベントを受け取れない。その後にスクリプトが実行されるので当然ながら。

  • jQuery.readyは問題なし。document.readyStateを見て、読み込み後であれば即時実行するから。

  • JavaScriptに依存するレイアウトは少なくともファーストビューでは避ける。カルーセルスライダーなどはJavaScriptオフでも表示崩れがないものを使う。


  • asyncを付けた要素間の実行順序は入れ替わる可能性があるので依存関係には要注意。ただしChromeでは今のところ実行順序の入れ替わりが確認できないので詳細は要調査。