🙆‍♀️

LightHouseスコア90越えのためにやったこと

に公開

高速で画像を表示するWebアプリを作りたいと思い挑戦しました。

これでもLCPが良くないのでもう少し調整が必要だなと思っています。
具体的に行ったことと、スコア80くらいはリリース前にあった方がいい理由などをお伝えします。

LightHouseスコア90超えのために

高パフォーマンスを出すために行った施策を列挙します。
試してないことがあればぜひやってみて下さい。

  • 画像変換はWebP
  • Next.jsを使用
  • 測定はシークレットモード+npm run build
  • jsの読み込み遅延
  • 最初の1枚のみ優先読み込み

LightHouseのスコアについて

指標 内容 理想値 改善策の例
FCP 最初に何かが表示されるまでの時間 < 1.8s priority, font-display, preload
LCP 一番大きな要素が表示されるまで < 2.5s WebP画像, SSR, CDN
TBT JS実行中のブロック時間合計 < 200ms(目標) JS分割, useMemo, 不要ライブラリ削除
CLS レイアウトのズレの合計 < 0.1 width/height, Skeleton UI
SI 視覚的な描画のスピード感 < 4.3s ローディング工夫, Lazy Load

画像変換はWebP

指標 内容 理想値 改善策の例
FCP 最初に何かが表示されるまでの時間 < 1.8s priority, font-display, preload
LCP 一番大きな要素が表示されるまで < 2.5s WebP画像, SSR, CDN
TBT JS実行中のブロック時間合計 < 200ms(目標) JS分割, useMemo, 不要ライブラリ削除
SI 視覚的な描画のスピード感 < 4.3s ローディング工夫, Lazy Load

画像のサイズやファイル形式は表示速度に影響します。
今回の目的は、表示速度だったので最も速いWebPに変換することにしました。
圧縮率もJPEGよりいいので画像を保存するときも容量を抑えられます

モバイル端末で見ることを前提に作っているため、1024pxにリサイズしてます。
モニターなどの大画面で見る場合は元のサイズを使用する方がいいでしょう。
ただ、モバイルファーストを考えると1024pxあれば十分です。

ちなみに、リサイズや画像変換に関しては別記事で解説しています。

https://zenn.dev/shuji0425/articles/6fd9b4bee38a19

Next.jsを使用

指標 内容 理想値 改善策の例
FCP 最初に何かが表示されるまでの時間 < 1.8s priority, font-display, preload
LCP 一番大きな要素が表示されるまで < 2.5s WebP画像, SSR, CDN
TBT JS実行中のブロック時間合計 < 200ms(目標) JS分割, useMemo, 不要ライブラリ削除
CLS レイアウトのズレの合計 < 0.1 width/height, Skeleton UI

表示速度を気にするとき、ViteかNext.jsで悩むと思います。
Viteの方が早いと思い、試験的に画像を表示させてみました。
しかし、Next.jsには画像に特化した<Image />というものがあります。
今回作成したWebアプリのように画像メインのサイトになることを踏まえるとNext.jsになりました。

<img>ではなく、<Image>の方がパフォーマンスが意識され、推奨されているのでおすすめです
<Image>では縦横を指定できるため、高パフォーマンスを狙うなら縦と横の大きさをDBに保存することをお勧めします。

測定はシークレットモード+npm run build

指標 内容 理想値 改善策の例
LCP 一番大きな要素が表示されるまで < 2.5s WebP画像, SSR, CDN
TBT JS実行中のブロック時間合計 < 200ms(目標) JS分割, useMemo, 不要ライブラリ削除
SI 視覚的な描画のスピード感 < 4.3s ローディング工夫, Lazy Load

シークレットモードで行うと、Cookieやキャッシュの影響を受けなくなります。
そのため、初見でアクセスする人のための速度を測定できます。
正確な測定を行うときは、シークレットモードにて行いましょう

LCPとTBT、SIに関してはビルド後の測定が必要です。
Next.jsではホットリロードを採用しているため本番で使用しないJsを多く使っています
不要なJsを省くことができるのでパフォーマンスが良くなります。

毎回ビルドすると1回の測定に5~10分はかかるため、ホットリロード状態の目安を紹介します。

  • LCP: 10~13s
  • TBT: 1500ms以下
  • SI: 3~4s

このくらいまで落とせていたら、ビルド後も高パフォーマンスが出ると思います。

Jsの読み込み遅延

指標 内容 理想値 改善策の例
LCP 一番大きな要素が表示されるまで < 2.5s WebP画像, SSR, CDN
TBT JS実行中のブロック時間合計 < 200ms(目標) JS分割, useMemo, 不要ライブラリ削除

ライブラリを使うときに注意してください。
今回のアプリでは、左右のスワイプとメイン画像に連動して、下の画像に枠をつける処理をSwiperを使って実装していました。

しかし、Jsの読み込みが多い影響で、LCPとTBTが悪化しました
そのため、motion.divを使い自作でスワイプを使っています。

motion.divも読み込みに時間がかかっていたので、不要な箇所は遅延読み込みさせるなど工夫を凝らしてます。
Js周りはTBTに影響を与えるため試行錯誤が重要です。

最初の1枚のみ優先読み込み

指標 内容 理想値 改善策の例
LCP 一番大きな要素が表示されるまで < 2.5s WebP画像, SSR, CDN
TBT JS実行中のブロック時間合計 < 200ms(目標) JS分割, useMemo, 不要ライブラリ削除

画像の読み込みは、LCPとTBTに影響します。
何も設定しない場合は、遅延読み込みになっているため最初の画像もしくは2~3枚目までは優先的に読み込むようにしましょう

今回は、メイン画像と下のサムネイル画像を1枚ずつ優先で読み込むようにしてます。
その影響もあり、高いパフォーマンスを出すことができました。

また、一度に多くの画像を描画するときは、10枚単位で無限スクロールなどを導入するのもおすすめです。

Googleアナリティクスでパフォーマンス低下

リリース前にLightHouseを90以上に保つことで、Googleアナリティクスでのパフォーマンス低下をカバーすることができます。

画像にもあるように、Googleアナリティクスを入れると5~10くらいパフォーマンスが落ちます

async
strategy="lazyOnload"

などを記載することでパフォーマンス低下を抑えれるので試してみて下さい
それでもたまに90まで行けばいい方かなと思っています。
下記はheadタグに実際に記載している内容です。

  <head>
    <Script
      async
      src="https://www.googletagmanager.com/gtag/js?id=G-GT5R8G8Q2P"
      strategy="lazyOnload"
    />
    <Script id="ga-init" strategy="lazyOnload">
      {`
        window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          requestIdleCallback(() => {
            gtag('js', new Date());
            gtag('config', 'G-GT5R8G8Q2P', { anonymize_ip: true });
        });
      `}
    </Script>
  </head>

解析ツールを使うことで、LCPとTBTが上昇します。
そのため、導入前はできるだけ低い値を保つようにしておくと悪化しても最小限に抑えられるとはずです。

別の解析ツールを導入

私の場合は、ブログ挫折組なのでGoogleサーチコンソールやGoogleアナリティクスを使いたいと思っています。
そのため、できる限りのパフォーマンス改善で妥協してます。

しかし、もっと高パフォーマンスを望むのであれば、別の解析ツールがいいでしょう。
Plausible AnalyticsCloudflare Web Analyticsなど有料にはなりますが、軽量で高速なのでおすすめです。

Discussion