以前、中央省庁のWebサイト37サイトのスピードを比較したとき、政府広報オンラインが最下位でした。
ということで、政府広報オンライントップページのフロントエンド高速化を勝手に提案します。
概要
Lighthouseのスコアで56
から91
に引き上げる提案です。
LighthouseはPagespeed Insightsの採点エンジンで、同じ内容で計測します。PageSpeed Insightsにも近い効果が見込めます。
動画で見ても違いは一目瞭然です。
← Before | After →
スコア改善のためにやったこと
- ロングタスクを解消する
- JavaScript Lazyloadをやめる
- document.writeによるコンテンツ挿入をやめる
- パブリックCDNを使わない
- bxSliderを最適化する
- 無駄に大きなバナー画像を軽量化する
- テキストリソースを通信中GZIP圧縮する
- 3rdパーティJSの読み込みを工夫する
- 政府インターネットテレビを工夫する
検証方法
私はこの政府広報オンラインの関係者ではありません。
しかし拙作PageSpeed Questで、任意のページについてフロントエンドの改変とそのLighthouseスコアへの影響を、ある程度の精度で仮説検証できます。
なお、Lighthouseは一定条件での試験に過ぎません。指標やスコアは実行タイミングで変動することがありますが、今回は簡易的に計測も1回ずつしか行っていません。読み物としてご了承ください。
変更内容
どのリソースをどのように変えたか、Gitで差分を公開します。詳しく知りたい方はこちらもご覧ください。
仮説検証と提案
では本題に入ります。まず改善前のLighthouseスコアと各指標の値がこちら。
Total Blocking Time
とSpeed Index
が悪い(赤)水準にあり、他の指標も要改善(オレンジ)となっています。
3rdパーティタグを一時退避する
まず3rdパーティタグは、アクセス解析や広告など主にクライアント都合で設置されるものであり、制作サイドでは普通は変更できません。
フロントエンドにおいて3rdパーティタグの影響は大きく、Webサイト自体はそこまで重くないのに、3rdパーティタグが理由でスコアが低下しているというケースはけっこうあります。
ここでは制作サイドで作用できる範囲にフォーカスするため、3rdパーティタグをいったん削除します。後ほど復元して対策を検討します。
変更点
このページも3rdパーティタグがそこそこ重く、それを外した結果Total Blocking Time
が990ms
→660ms
と一気に1/3も改善しました。
政府インターネットテレビを一時退避する
このページでとにかく重いのが政府インターネットテレビ
のインライン動画プレーヤーです。
動画データではなくプレーヤーのJavaScriptが重いです。ほんとにクソ重い。
こちらもおそらく外部提供の3rdパーティJavaScriptのようなもので、あまり変更はできなさそうです。いったん削除して他の点にフォーカスし、後ほど対策を検討します。
変更点
Total Blocking Time
が660ms
→50ms
となり、ブロッキングの問題はほぼ解消されました。
Speed Index
も18.7
→9.4
と大幅に改善されました。
ロングタスクを解消する
この時点で開発者ツールのPerformance
タブを見ると、DOMContentLoaded
を起点に長いタスクがメインスレッドを占有しています。
このようにメインスレッドを50ms以上占有するタスクをロングタスク
と言います。
- ロングタスクの間はユーザーの操作に即座に反応せず、フリーズしたような状態になる
-
Total Blocking Time
はロングタスク基準超過(50msを超えた)分の合計で計算される
ロングタスクは一匹残らず駆逐する決意で臨みます。
jQueryハンドラ乱用問題
上記のロングタスクは、ページ初期化処理の詰め込み過ぎが原因です。これもあるあるです。
jQueryでは以下のように手軽にページ初期化時の処理を書くことができます。
これには罠があり、記述箇所はバラバラでも実行時はそれらがまとめてぶっ続けで実行されるので、無頓着に使うと意図せずロングタスク化します。今の状況はまさにそれです。
$(function() {
// 初期化処理1 30ms 非ロングタスク
});
$(function() {
// 初期化処理2 40ms 非ロングタスク
});
// 初期化処理1と2は続けて実行される
// 30ms + 40ms = 70ms > 50ms ロングタスク
一番手軽なのは、こまめにsetTimeout
で括ることです。イベントループに一旦スレッドを返して別タスクに分離でき、ロングタスクへの肥大化を回避できます。
$(function() {
setTimeout(function() {
// 重い初期化処理 → 別タスクに分離
});
});
この考えに基づき、比較的占有時間の長い処理を逃してあげた結果がこちらです。
変更点
狙い通りロングタスクを駆逐し、Total Blocking Time
を0ms
にできました。
ファーストビューの描画タイミングに鎮座していたロングタスクを散らせたのでSpeed Index
も改善されました。
JavaScript Lazyloadをやめる
昔流行ったJavaScript Lazyloadですが、今はブラウザがネイティブで同様の機能をサポートしています。
JavaScriptで頑張るのは逆に計算量の無駄遣いなので即座にやめましょう。
変更点
指標上は変化はありませんが、JavaScript Lazyloadも先ほどのDOMContentLoaded
のロングタスクに関与していました。そのリスクは解消できました。
document.writeによるコンテンツ挿入をやめる
おそらくCMSやサーバーの都合によるものと推測しますが、JavaScriptを用いてコンテンツを動的に挿入(インクルード)する処理が随所で使われています。
その実装にいにしえの秘術 document.write
が用いられています。
document.write
はスクリプトをそこから動かせず、非同期にもできません。呪いのようなレンダリングブロック要因になるので、使わないのが吉です。
ここではDOM操作によるインクルード処理に変換し、script
要素もできるだけasync
にします。
変更点
実はこれでFirst Content Paint
が改善できるかな、と期待しましたが指標に変化はありませんでした。やはりやってみないとわからないものです。
パブリックCDNを使わない
jQueryをパブリックCDNから取得しています。
個人的にこの手法は望ましくないと考えてて、CSSと早いタイミングで欲しいJavaScriptはできるだけHTMLと同じサーバーからの配信をお勧めしてます。
- パブリックCDNは経験上、日本では遅い
- 別ドメインからの配信にはDNSルックアップやSSLハンドシェイクが生じる
以上がその理由です。特にjQuery本体は依存元が多く、早めに欲しいファイルです。
トラフィックの分散という意図があるかもしれませんが、画像のトラフィックに比べるとCSSやJavaScriptは相当小さく、割に合いません。
これもFirst Contentful Paint
を…と期待したものの、指標の変化はなしでした。
bxSliderを最適化する
次にメインビジュアルで用いられているカルーセルスライダー bxSlider
の最適化を図ります。
個人的にはファーストビューのカルーセルスライダーは滅びろと思っていますし、このページならCSSベースのもっと軽量なライブラリを使う方がよいと思いますが、bxSliderで頑張ってみます。
今回、問題を感じたのは次の2点です。
- スライダーが起動するまでシーンが縦並びに表示され、起動によりレイアウトが変化する
- bxSliderがDOMの再構築をするので画像読み込み中に起動すると表示がチラつく
ここまでCumurative Layout Shift
の評価がイマイチなのは主に1.のせいです。
調整したのは、以下です。
- スライダーの起動前も1シーン目のみが表示されるようにCSSを調整
- 1シーン目の画像が読み込み終わるまでスライダーの起動を待機するようJavaScriptを調整
その結果、Cumulative Layout Shift
は狙い通り改善されました。チラつきもなくなりましたが、Largest Contentful Paint
とSpeed Index
が大幅に悪化してしまいました!
無駄に大きなバナー画像を軽量化する
bxSliderの最適化で逆に指標が悪化したのは、こちらの1枚目の画像ファイルが無駄に大きすぎるためです。
画像はわざわざWebPも用意して頑張っているようですが、PCでも高々 800 x 450
の表示領域に対し、画像自体が 4481 x 2521
もあります。ファイルサイズはなんと1枚で 2.3 MB
もあります。
これは単純にアップロードする画像の間違いだと思われます。
最近はWebの専門ではない人がサイトを更新することが多いですが、CMS等で解像度の正規化を入れた方がいいです。ほんと素人さんだとこのようにびっくりする画像を上げたりするので…
ここでは、この2.3 MB
の異常に大きな画像bn_u_20221001_sl.webp
を44 KB
に軽量化しました。
# いったんPNGにする
dwebp -o bn_u_20221001_sl.webp.png bn_u_20221001_sl.webp
# 適切な解像度にリサイズする
convert bn_u_20221001_sl.webp.png -resize 800x450 bn_u_20221001_sl.webp.resize.png
# pngquantで減色する
pngquant --output bn_u_20221001_sl.webp.resize.quant.png 32 bn_u_20221001_sl.webp.resize.png
# 再度WebPにする
cwebp -lossless -o bn_u_20221001_sl.min.webp bn_u_20221001_sl.webp.resize.quant.png
変更点
これでLargest Contentful Paint
とSpeed Index
を正常化できました。
テキストリソースを通信中GZIP圧縮する
このページではテキストリソース(HTML、CSS、JavaScript、SVG)が、配信中に圧縮されていません。
圧縮と解凍による負荷を避けるという理由なのか、単に設定忘れなのかはわかりませんが、圧縮はやった方がいいです。20年前ならいざしらず、インターネットにデータを流すことに比べたらデータの圧縮と解凍なんて今や空気みたいなものです。
変更点
実際、テキストリソースを通信中GZIP圧縮することでFirst Contentful Paint
、Largest Contentful Paint
が改善されました。
3rdパーティタグと政府インターネットテレビ
3rdパーティタグと政府インターネットテレビの問題はいったん棚上げしていますが、ここまででスコア95
とかなり改善できました。
気になる点はありますが、ひとまずスコア改善はここまでにします。
退避した3rdパーティタグを工夫する
3rdパーティタグをonloadで挿入する
ページ本来のコンテンツと3rdパーティタグはメインスレッドに同居し、時間を常に食い合う関係にあります。
コンテンツの表示中に3rdパーティJavaScriptが動き出すと、その分本来の機能は遅延します。
ここでは退避していた3rdパーティタグを復元しますが、script
要素を挿入するタイミングをwindow.onload
に遅延させてみました。
変更点
せっかく0ms
にできたTotal Blocking Time
は悪化しますが、本来のコンテンツ表示にCPUを優先的に譲れるので、少しマシになるはずです。
3rdパーティタグは常に断捨離を
このページでは比較的3rdパーティタグは少ないですが、一般企業のサイトだと、Google Tag Managerが責任者不明のタグのジャングルになっているケースも珍しくありません。
空気のようなものと思って無頓着に追加するうちに主従が完全に逆転し、本来のコンテンツ表示より3rdパーティのJavaScriptが何倍もCPUを消費していることもあります。
政府広報オンラインでも、トップページについて言えば以下のFacebook SDKは必要そうに見えません。
<script>(function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/ja_JP/sdk.js#xfbml=1&version=v2.9";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
部屋の片付けと一緒で、ひとつずつ捨てる決断をするのは大変です。なので3rdパーティタグは一度全部削除してしまって、本当に困ったタグから改めて追加するようなことをしてもよいと思います。
政府インターネットテレビを工夫する
最後に、最大の問題である政府インターネットテレビをどうにかしたいと思います。
ぶっちゃけ再生する人はかなり少ないと思われます。それなのに全員にインライン再生用のクソ重いJavaScriptを問答無用に読み込ませて使用感を悪化させているのはあまりに非効率です。
いくつかアイデアがあります。
- video要素を使う
- クリックしてからプレーヤーを読み込み自動再生する
- ページの下部に配置しスクロールに応じてプレーヤーを読み込む
- iframeでメインスレッドから逃す
video要素を使う
大人の事情でダメそうですが、おそらく一番シンプルです。
クリックしてからプレーヤーを読み込み自動再生する
プレーヤーをあらかじめ読み込んで再生に備えるのではなく、クリックされたからプレーヤーを読み込み、自動再生するという案です。クリックから再生まで時間はかかりますが、再生率が低いのであれば投機的にこの方が正解でしょう。
この手法であれば初期状態で必要なのは動画のポスター画像だけなので負荷はほぼありません。
変更点
ページの下部に配置しスクロールに応じてプレーヤーを読み込む
政府インターネットテレビをファーストビューの一等地に置くのではなく、ページ下部に配置するという案です。
これもおそらく趣旨に反してNGと思われますが、ページ下部にあればプレーヤーの起動はページ読み込みと同時ではなく、ある程度スクロールしてからに遅延できます。ファーストビューの体験を改善できます。
iframeでメインスレッドから逃す
インライン動画に見えて実はiframe
になっているという案です。
最初はこの案がよいと思ったんですが、Chromeではiframeのオリジンが同じTLDだとメインスレッドを共有するようで、効果なしでした。
別のTLDではiframeは別スレッドになったものの、Lighthouse上ではTotal Blocking Time
が合算されてしまい、スコア上は目立った成果が出せませんでした。実質としてはこれでよいという気はしますが。
結論
3rdパーティタグはonloadのタイミングに遅延させ、政府インターネットテレビはサムネイルだけを表示し、クリックでプレーヤーを読み込むようにする、というのが落とし所かと考えます。
それによるスコア改善結果がこちらです。