Webページ表示速度高速化、始めます！

# 背景
- 先週からWebページ高速化を勉強しはじめて、なんとか最初の改善リリースを終えました。
  - TOPページとLPをリニューアルしたのですが、表示速度が極端に低くなった([PageSpeed Insights
](https://developers.google.com/speed/pagespeed/insights/)で5/100点！)ため、急いでWebページ高速化に取り組まざるをえなくなった。最終的には数日程度で50点近くまで上げることができた。
- とにかく急いでやったプロジェクトだったんですが、似たような状況に追い込まれた人のために、やったことを残しておきます。

# やったこと
## 1. 「初歩からのPageSpeed Insights」 を読む

とりあえずまず以下の本を読みました。

[重いサイトを軽くする、Webページ表示速度の高速化10の基本: 初歩からのPageSpeed Insights (にししふぁくとりー叢書)](https://www.amazon.co.jp/%E9%87%8D%E3%81%84%E3%82%B5%E3%82%A4%E3%83%88%E3%82%92%E8%BB%BD%E3%81%8F%E3%81%99%E3%82%8B%E3%80%81Web%E3%83%9A%E3%83%BC%E3%82%B8%E8%A1%A8%E7%A4%BA%E9%80%9F%E5%BA%A6%E3%81%AE%E9%AB%98%E9%80%9F%E5%8C%9610%E3%81%AE%E5%9F%BA%E6%9C%AC-%E5%88%9D%E6%AD%A9%E3%81%8B%E3%82%89%E3%81%AEPageSpeed-Insights-%E3%81%AB%E3%81%97%E3%81%97%E3%81%B5%E3%81%81%E3%81%8F%E3%81%A8%E3%82%8A%E3%83%BC%E5%8F%A2%E6%9B%B8-%E8%A5%BF%E6%9D%91-ebook/dp/B07T17QSQ5/ref=sr_1_4?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=%E9%AB%98%E9%80%9F%20%E3%82%B5%E3%82%A4%E3%83%88&qid=1604048877&sr=8-4)

- Amazon Unlimitedに加入してれば無料で読める
- 少ない分量でまとまっている。
- 全部読まなくても、自分に関係がありそうな部分だけを拾い読む -> 可能な施策だけ着手 の流れが容易。

まずは、この本の内容と、自分のWebサイトのPageSpeed Insightsの結果を見比べて、効果がありそうなものから着手していきました。基本的な知識は全部この本で得られたので、この記事では、上記の本で足りなかったことを解説していきます。

## 2. 外部js, youtubeなどの超遅延ロード

**超遅延ロード** というのは私の造語ですが、 `defer` などを用いた単なるjsの遅延ロードではなく、**「Userが何らかのアクションをしたらロードする」というアクロバティックな遅延ロード手法**です。[この記事](https://yws.tokyo/twitter-instagram-lazyload/)で紹介されてました。

うちのサイトの場合、**tweetのjavascriptの埋め込み** と **youtubeの埋め込み** が表示速度の悪化の大部分の原因を占めていたので、 これが最も効果があった施策でした。（あと、tweetとyoutubeはページのかなり下の方のコンテンツだったので、ロードを遅らせる合理的な理由もありました）

以下のコードで、Userの何らかのアクションに引っかけて、`<script>` タグや `<iframe>` などのDOMをinsertします。上記記事からコードの大部分をお借りしております。

```javascript:lazy_load.js
(function(window, document) {
  function insertResource() {

    // twitterの読み込み
    var tw_script = document.createElement('script');
    tw_script.type = 'text/javascript';
    tw_script.defer = true;
    tw_script.src = 'https://platform.twitter.com/widgets.js';
    document.getElementById('js-twitter-load').appendChild(tw_script); // 対象のページに<div id="js-twitter-load"></div> を埋め込んでおく

    // youtubeの読み込み
    var youtube = document.createElement('iframe');
    youtube.className = "youtube__iframe";
    youtube.src = "https://www.youtube.com/embed/xxxxxxxxx";
    youtube.frameborder = 0
    youtube.allow = "autoplay; encrypted-media"
    youtube.allallowfullscreenow = ""
    document.getElementById('js-youtube').appendChild(youtube); // 対象のページに<div id="js-youtube"></div> を埋め込んでおく
  }

  // 遅延読込み
  var lazyLoad = false;
  function onLazyLoad() {
    if (lazyLoad === false) {
      // 複数呼び出し回避 + イベント解除
      lazyLoad = true;
      window.removeEventListener('scroll', onLazyLoad);
      window.removeEventListener('mousemove', onLazyLoad);
      window.removeEventListener('mousedown', onLazyLoad);
      window.removeEventListener('touchstart', onLazyLoad);
      window.removeEventListener('keydown', onLazyLoad);

      insertResource();
    }
  }
  window.addEventListener('scroll', onLazyLoad);
  window.addEventListener('mousemove', onLazyLoad);
  window.addEventListener('mousedown', onLazyLoad);
  window.addEventListener('touchstart', onLazyLoad);
  window.addEventListener('keydown', onLazyLoad);
  window.addEventListener('load', function() {
    // ドキュメント途中までスクロールしている場合（更新時 or ページ内リンク）
    if (window.pageYOffset) {
      onLazyLoad();
    }
  });
})(window, document);
```

あとは `lazy_load.js` 自体を通常の遅延読み込みで埋め込みます。

```html:index.html
<script type="text/javascript" src="/js/lazy_load.js" defer="defer" />
```

## 3. CSS と WebFont の遅延ロード

`defer` を使えば簡単にできるjavascriptの遅延ロードと違って、CSSの遅延ロードはちょっと面倒ですよね。
以下の記事で簡単な方法が紹介されていたので、活用しました。

[CSSを非同期ロードする最も簡単な方法](https://qiita.com/rana_kualu/items/95a7adf8420ea2b9f657)

`media="print" onload="this.media='all'"` を追加するだけ。便利。

以下は [Uikit](https://getuikit.com/) のcssを埋め込む時の例です。

```html:index.html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.5.9/dist/css/uikit.min.css" media="print" onload="this.media='all'" />
```

WebFont(例：GoogleFontsの"Noto Sans JP")でも同様のことができます。
1. [Google Fonts](https://fonts.google.com/specimen/Noto+Sans+JP?sidebar.open=true&selection.family=Noto+Sans+JP:wght@400;700) から 埋め込み用の<link>タグを取得
2. `media=”print” onload="this.media='all'"` を追記する

```html:index.html
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet" media=”print” onload="this.media='all'">
```

ファーストビューの描画に必要なCSSだけを同期的に読み込み、それ以外のCSSやWebFontは全部遅延読み込みにしてしまいます。

## 4. `.webp` 形式の画像の導入

上記までの施策で、うちのサイトの点数はだいたい改善されたのですが、残ったのが画像の重さ問題。
PageSpeed Insightsがしつこく勧めてくるので、.webp形式の画像の導入を決めました。
.webp 形式は .jpg や .png より軽いのが利点ですが、対応していないブラウザも多いので、他の形式の画像と併用する必要があります。

`<img>` タグを使用している場合は、以下の書き方でOK。

```html:index.html
<picture class="thumb">
  <source srcset="/img/top/thumb.webp" type="image/webp">
  <img class="image" src="/img/top/thumb.jpg" loading="lazy" />
</picture>
```

問題は CSSの background-image などで画像を指定している場合ですね。
[modernizr.js](https://modernizr.com/)を使用することで、ブラウザの対応状況に合わせてCSSを切り替えることができます。詳しくは以下記事をどうぞ。

[WebP（ウェッピー）を導入してみよう](https://akros-ac.jp/13306/)

## 5. Resource Hintsの使用

そこまでやる必要があるのかはよくわからなかったんですが、 **Resource Hints** なるものを使うことで、必要になるであろうリソースを先にブラウザに伝えることができるようです。
今回の場合、ファーストビューのメインビジュアル画像をCSSのbackground-imageで指定してたので、**HTML -> CSS -> 画像ファイル** という３工程のリクエストチェーンになってしまうので、読み込み開始を早めるために使用しました。

```html:index.html
<link rel="preload" href="/img/top/mainvisual.webp" as="image">
```

これにより、ファーストビューのメインビジュアル画像のリクエストチェーンを **HTML -> 画像ファイル** の2工程に短縮できます。
体感でしかないけど、ファーストビューの表示がちょっとだけ早くなったような気はする。

# 結果
これだけやったところ、5/100点から50/100点にまでは上がったんですが、まだ Largest Contentful Paint（LCP） の点数が辛くてそれ以上にはならないようです。なんでなんや・・・。
もうちょっと勉強したらまた続きを書こうと思います。

# 参考
- [TwitterやInstagramの埋め込みを遅延読み込みして高速表示しよう](https://yws.tokyo/twitter-instagram-lazyload/)
- [CSSを非同期ロードする最も簡単な方法](https://qiita.com/rana_kualu/items/95a7adf8420ea2b9f657)
- [WebP（ウェッピー）を導入してみよう](https://akros-ac.jp/13306/)
- [link要素によるResource Hintsを使用してリソースの先読みを行う](https://sbfl.net/blog/2017/11/19/resource-hints/)