政府広報オンラインのフロントエンド高速化を勝手に提案する

以前、中央省庁のWebサイト37サイトのスピードを比較したとき、[政府広報オンライン](https://www.gov-online.go.jp/)が最下位でした。

![貼り付けた画像_2023_11_08_5_25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/3b905143-4c4c-c697-b351-7d7f4bde872c.png)
[CrUX 霞ヶ関サイトスピード番付](https://sitespeed-hikaku.com/reports/V6FGcFU3I2rlkXsT3oVt/compare-density)

ということで、政府広報オンライントップページのフロントエンド高速化を勝手に提案します。

# 概要

[Lighthouse](https://developer.chrome.com/ja/docs/lighthouse/)のスコアで`56`から`91`に引き上げる提案です。

Lighthouseは[Pagespeed Insights](https://pagespeed.web.dev/?hl=ja)の採点エンジンで、同じ内容で計測します。PageSpeed Insightsにも近い効果が見込めます。

![score-before-after1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/8ecb93c2-8dd3-719b-7e20-d22ed8a2c3db.png)

動画で見ても違いは一目瞭然です。

← Before | After →

<iframe width="522" height="928" src="https://www.youtube.com/embed/6h5KJtThA_k" title="政府広報オンラインのフロントエンド高速化を勝手に提案する" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

## スコア改善のためにやったこと

- ロングタスクを解消する
- JavaScript Lazyloadをやめる
- document.writeによるコンテンツ挿入をやめる
- パブリックCDNを使わない
- bxSliderを最適化する
- 無駄に大きなバナー画像を軽量化する
- テキストリソースを通信中GZIP圧縮する
- 3rdパーティJSの読み込みを工夫する
- 政府インターネットテレビを工夫する

## 検証方法

私はこの[政府広報オンライン](https://www.gov-online.go.jp/)の関係者ではありません。

しかし拙作[PageSpeed Quest](https://github.com/ideamans/pagespeed-quest/blob/main/README.ja.md)で、任意のページについてフロントエンドの改変とそのLighthouseスコアへの影響を、ある程度の精度で仮説検証できます。

なお、Lighthouseは一定条件での試験に過ぎません。指標やスコアは実行タイミングで変動することがありますが、今回は簡易的に計測も1回ずつしか行っていません。読み物としてご了承ください。

## 変更内容

どのリソースをどのように変えたか、Gitで差分を公開します。詳しく知りたい方はこちらもご覧ください。

https://github.com/ideamans/gov-online-pagespeed-demo

# 仮説検証と提案

では本題に入ります。まず改善前のLighthouseスコアと各指標の値がこちら。

![snapshot-score.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/40adacbc-1b4f-6776-8c3c-84904f50cf9c.png)

`Total Blocking Time`と`Speed Index`が悪い(赤)水準にあり、他の指標も要改善(オレンジ)となっています。


## 3rdパーティタグを一時退避する

まず3rdパーティタグは、アクセス解析や広告など主にクライアント都合で設置されるものであり、制作サイドでは普通は変更できません。

フロントエンドにおいて3rdパーティタグの影響は大きく、**Webサイト自体はそこまで重くないのに、3rdパーティタグが理由でスコアが低下**しているというケースはけっこうあります。

ここでは制作サイドで作用できる範囲にフォーカスするため、3rdパーティタグをいったん削除します。後ほど復元して対策を検討します。

![stash-3rdjs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/59b41152-b49d-f728-45cc-d93740a72a85.png)

### 変更点

https://github.com/ideamans/gov-online-pagespeed-demo/activity?ref=stash-3rdjs

このページも3rdパーティタグがそこそこ重く、それを外した結果`Total Blocking Time`が`990ms`→`660ms`と一気に1/3も改善しました。

## 政府インターネットテレビを一時退避する

このページでとにかく重いのが`政府インターネットテレビ`のインライン動画プレーヤーです。

![Cursor_と_政府広報オンライン　あしたの暮らしをわかりやすく.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/5ec1dfb3-d405-0533-a2c5-ac62c304be87.png)

動画データではなくプレーヤーのJavaScriptが重いです。ほんとにクソ重い。

こちらもおそらく外部提供の3rdパーティJavaScriptのようなもので、あまり変更はできなさそうです。いったん削除して他の点にフォーカスし、後ほど対策を検討します。

![stash-nettv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/cf6be08b-bca9-68b4-4e4d-52c5fb7bf458.png)

### 変更点

https://github.com/ideamans/gov-online-pagespeed-demo/activity?ref=stash-nettv

`Total Blocking Time`が`660ms`→`50ms`となり、ブロッキングの問題はほぼ解消されました。

`Speed Index`も`18.7`→`9.4`と大幅に改善されました。


## ロングタスクを解消する

この時点で開発者ツールの`Performance`タブを見ると、`DOMContentLoaded`を起点に長いタスクがメインスレッドを占有しています。

![dcl-longtask.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/7edcb6c9-e5bd-c54e-5b2c-cb78c3fd0e3f.png)

このようにメインスレッドを50ms以上占有するタスクを`ロングタスク`と言います。

- ロングタスクの間はユーザーの操作に即座に反応せず、フリーズしたような状態になる
- `Total Blocking Time`はロングタスク基準超過(50msを超えた)分の合計で計算される

ロングタスクは一匹残らず駆逐する決意で臨みます。

### jQueryハンドラ乱用問題

上記のロングタスクは、**ページ初期化処理の詰め込み過ぎが原因**です。これもあるあるです。

jQueryでは以下のように手軽にページ初期化時の処理を書くことができます。

これには罠があり、**記述箇所はバラバラでも実行時はそれらがまとめてぶっ続けで実行される**ので、無頓着に使うと意図せずロングタスク化します。今の状況はまさにそれです。

```javascript
$(function() {
  // 初期化処理1 30ms 非ロングタスク
});

$(function() {
  // 初期化処理2 40ms 非ロングタスク
});

// 初期化処理1と2は続けて実行される
// 30ms + 40ms = 70ms > 50ms ロングタスク
```

一番手軽なのは、こまめに`setTimeout`で括ることです。イベントループに一旦スレッドを返して別タスクに分離でき、ロングタスクへの肥大化を回避できます。

```javascript
$(function() {
  setTimeout(function() {
    // 重い初期化処理 → 別タスクに分離
  });
});
```

この考えに基づき、比較的占有時間の長い処理を逃してあげた結果がこちらです。

![break-longtask.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/e46239ab-a62a-fbe0-e7bb-954f9709dddf.png)

### 変更点

https://github.com/ideamans/gov-online-pagespeed-demo/activity?ref=break-longtask

狙い通りロングタスクを駆逐し、`Total Blocking Time`を`0ms`にできました。

ファーストビューの描画タイミングに鎮座していたロングタスクを散らせたので`Speed Index`も改善されました。


## JavaScript Lazyloadをやめる

昔流行ったJavaScript Lazyloadですが、今はブラウザがネイティブで同様の機能をサポートしています。

https://developer.mozilla.org/ja/docs/Web/API/HTMLImageElement/loading

JavaScriptで頑張るのは逆に計算量の無駄遣いなので即座にやめましょう。

![no-lazyload.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/963846a5-e833-6d88-41c9-8131f8f623c5.png)

### 変更点

https://github.com/ideamans/gov-online-pagespeed-demo/activity?ref=no-lazyload

指標上は変化はありませんが、JavaScript Lazyloadも先ほどの`DOMContentLoaded`のロングタスクに関与していました。そのリスクは解消できました。


## document.writeによるコンテンツ挿入をやめる

おそらくCMSやサーバーの都合によるものと推測しますが、JavaScriptを用いてコンテンツを動的に挿入(インクルード)する処理が随所で使われています。

その実装にいにしえの秘術 `document.write` が用いられています。

`document.write`はスクリプトをそこから動かせず、非同期にもできません。呪いのようなレンダリングブロック要因になるので、使わないのが吉です。

ここではDOM操作によるインクルード処理に変換し、`script`要素もできるだけ`async`にします。

![no-document-write.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/4e014a7f-eceb-6495-7cf6-6807f954f7e0.png)

### 変更点

https://github.com/ideamans/gov-online-pagespeed-demo/activity?ref=no-document-write

実はこれで`First Content Paint`が改善できるかな、と期待しましたが指標に変化はありませんでした。やはりやってみないとわからないものです。


## パブリックCDNを使わない

jQueryをパブリックCDNから取得しています。

https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js

個人的にこの手法は望ましくないと考えてて、CSSと早いタイミングで欲しいJavaScriptはできるだけHTMLと同じサーバーからの配信をお勧めしてます。

- パブリックCDNは経験上、日本では遅い
- 別ドメインからの配信にはDNSルックアップやSSLハンドシェイクが生じる

以上がその理由です。特にjQuery本体は依存元が多く、早めに欲しいファイルです。

トラフィックの分散という意図があるかもしれませんが、画像のトラフィックに比べるとCSSやJavaScriptは相当小さく、割に合いません。

![fast-jquery.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/bee5975b-dd03-235f-b463-261e797e28dd.png)

これも`First Contentful Paint`を…と期待したものの、指標の変化はなしでした。


## bxSliderを最適化する

次にメインビジュアルで用いられているカルーセルスライダー `bxSlider` の最適化を図ります。

個人的にはファーストビューのカルーセルスライダーは滅びろと思っていますし、このページならCSSベースのもっと軽量なライブラリを使う方がよいと思いますが、bxSliderで頑張ってみます。

今回、問題を感じたのは次の2点です。

1. スライダーが起動するまでシーンが縦並びに表示され、起動によりレイアウトが変化する
1. bxSliderがDOMの再構築をするので画像読み込み中に起動すると表示がチラつく

ここまで`Cumurative Layout Shift`の評価がイマイチなのは主に1.のせいです。

調整したのは、以下です。

1. スライダーの起動前も1シーン目のみが表示されるようにCSSを調整
1. 1シーン目の画像が読み込み終わるまでスライダーの起動を待機するようJavaScriptを調整

![slider-layout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/8d73f6a4-6c75-3a94-497f-93c8c0dc435d.png)

その結果、`Cumulative Layout Shift`は狙い通り改善されました。チラつきもなくなりましたが、`Largest Contentful Paint`と`Speed Index`が大幅に悪化してしまいました！

## 無駄に大きなバナー画像を軽量化する

bxSliderの最適化で逆に指標が悪化したのは、こちらの1枚目の画像ファイルが無駄に大きすぎるためです。

画像はわざわざWebPも用意して頑張っているようですが、PCでも高々 `800 x 450` の表示領域に対し、画像自体が `4481 x 2521` もあります。ファイルサイズはなんと1枚で `2.3 MB`もあります。

![Cursor_と_政府広報オンライン　あしたの暮らしをわかりやすく.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/28b0c19a-7013-c7b2-1cbb-5f1f5365d304.png)

これは単純にアップロードする画像の間違いだと思われます。

最近はWebの専門ではない人がサイトを更新することが多いですが、CMS等で解像度の正規化を入れた方がいいです。ほんと素人さんだとこのようにびっくりする画像を上げたりするので…

ここでは、この`2.3 MB`の異常に大きな画像`bn_u_20221001_sl.webp`を`44 KB`に軽量化しました。

```bash
# いったん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
```

![optimize-banner.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/23c9d1b6-999a-9d9d-99c9-0ecf14a25d97.png)

### 変更点

https://github.com/ideamans/gov-online-pagespeed-demo/activity?ref=optimize-banner

これで`Largest Contentful Paint`と`Speed Index`を正常化できました。

## テキストリソースを通信中GZIP圧縮する

このページではテキストリソース(HTML、CSS、JavaScript、SVG)が、配信中に圧縮されていません。

圧縮と解凍による負荷を避けるという理由なのか、単に設定忘れなのかはわかりませんが、圧縮はやった方がいいです。20年前ならいざしらず、インターネットにデータを流すことに比べたらデータの圧縮と解凍なんて今や空気みたいなものです。

![gzip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/27457d5b-4da7-909a-f7f1-e3b34925ebdd.png)

### 変更点

https://github.com/ideamans/gov-online-pagespeed-demo/activity?ref=gzip

実際、テキストリソースを通信中GZIP圧縮することで`First Contentful Paint`、`Largest Contentful Paint`が改善されました。

# 3rdパーティタグと政府インターネットテレビ

3rdパーティタグと政府インターネットテレビの問題はいったん棚上げしていますが、ここまででスコア`95`とかなり改善できました。

![gzip-with-score.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/4bd1406d-977a-24b7-f2cb-3d2896f6b6de.png)

気になる点はありますが、ひとまずスコア改善はここまでにします。

## 退避した3rdパーティタグを工夫する

### 3rdパーティタグをonloadで挿入する

ページ本来のコンテンツと3rdパーティタグはメインスレッドに同居し、時間を常に食い合う関係にあります。

コンテンツの表示中に3rdパーティJavaScriptが動き出すと、その分本来の機能は遅延します。

ここでは退避していた3rdパーティタグを復元しますが、`script`要素を挿入するタイミングを`window.onload`に遅延させてみました。

![delay-3rdjs-onload.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/60899b12-6be8-955f-872f-a4de04b7945c.png)

#### 変更点

https://github.com/ideamans/gov-online-pagespeed-demo/activity?ref=delay-3rdjs-onload

せっかく`0ms`にできた`Total Blocking Time`は悪化しますが、本来のコンテンツ表示にCPUを優先的に譲れるので、少しマシになるはずです。

## 3rdパーティタグは常に断捨離を

このページでは比較的3rdパーティタグは少ないですが、一般企業のサイトだと、Google Tag Managerが責任者不明のタグのジャングルになっているケースも珍しくありません。

空気のようなものと思って無頓着に追加するうちに主従が完全に逆転し、本来のコンテンツ表示より3rdパーティのJavaScriptが何倍もCPUを消費していることもあります。

政府広報オンラインでも、トップページについて言えば以下のFacebook SDKは必要そうに見えません。

```html
	<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を問答無用に読み込ませて使用感を悪化させているのはあまりに非効率です。

いくつかアイデアがあります。

1. video要素を使う
1. クリックしてからプレーヤーを読み込み自動再生する
1. ページの下部に配置しスクロールに応じてプレーヤーを読み込む
1. iframeでメインスレッドから逃す

### video要素を使う

大人の事情でダメそうですが、おそらく一番シンプルです。

### クリックしてからプレーヤーを読み込み自動再生する

プレーヤーをあらかじめ読み込んで再生に備えるのではなく、クリックされたからプレーヤーを読み込み、自動再生するという案です。クリックから再生まで時間はかかりますが、再生率が低いのであれば投機的にこの方が正解でしょう。

この手法であれば初期状態で必要なのは動画のポスター画像だけなので負荷はほぼありません。

![optimized.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/b736a18c-4532-5ec9-b55f-a7221b5e7c50.png)

#### 変更点

https://github.com/ideamans/gov-online-pagespeed-demo/activity?ref=optimized

### ページの下部に配置しスクロールに応じてプレーヤーを読み込む

政府インターネットテレビをファーストビューの一等地に置くのではなく、ページ下部に配置するという案です。

これもおそらく趣旨に反してNGと思われますが、ページ下部にあればプレーヤーの起動はページ読み込みと同時ではなく、ある程度スクロールしてからに遅延できます。ファーストビューの体験を改善できます。

### iframeでメインスレッドから逃す

インライン動画に見えて実は`iframe`になっているという案です。

最初はこの案がよいと思ったんですが、Chromeではiframeのオリジンが同じTLDだとメインスレッドを共有するようで、効果なしでした。

別のTLDではiframeは別スレッドになったものの、Lighthouse上では`Total Blocking Time`が合算されてしまい、スコア上は目立った成果が出せませんでした。実質としてはこれでよいという気はしますが。

## 結論

3rdパーティタグはonloadのタイミングに遅延させ、政府インターネットテレビはサムネイルだけを表示し、クリックでプレーヤーを読み込むようにする、というのが落とし所かと考えます。

それによるスコア改善結果がこちらです。

![score-before-after1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35178/8ecb93c2-8dd3-719b-7e20-d22ed8a2c3db.png)