Home Blogs Lighthouse v6.0用のWebサイトの最適化
Applications

Lighthouse v6.0用のWebサイトの最適化

About The Author

Outline

Layer0は、オープンソースのeコマースPWAフレームワークであるReact Storefrontの主要な貢献者である。 今年の初めに、次のような多くの新機能と最適化を提供しました。 React Storefront v7最も重要なのは、Next.jsへの移行と、useStateフックやコンテキストAPIなどのReactの状態管理のための新しい組み込み機能を支持するためのいくつかのキー依存関係(MobXなど)の削除です。 その結果、ブラウザバンドルのサイズがほぼ半分に削減されました。

これは当時の素晴らしい進歩であり、PageSpeed Insights (PSI)によって測定された80年代から90年代の典型的なReact StorefrontアプリのLighthouse (v5.7)のパフォーマンススコアを向上させるのに役立ちまし た。 コンテキストについては、Lighthouse v5.7で上位の500 eコマースWebサイトの99%を83+のスコアが上回っています。 Lighthouse v6.0が爆弾のように落下し、全員のパフォーマンススコアを消し去るようになると、バンドルの削減が今後数か月でどれほど重要であるかを理解していませんでした。

v6.0が終了したときに、主要なeコマースWebサイトのPSIで測定されたLighthouseスコアの分布がどのように変化したかをご覧ください。

この記事では、React StorefrontのLighthouse v6.0スコアをどのように改善したかを共有しますが、このテクニックは他のフレームワークにも適用できます。

また、Googleは2020年5月28日に、2021年にサイトをランク付けするために使用する特定の指標を発表したことに注意することが重要 です。 Lighthouseのパフォーマンススコアは使用されませんが、そのスコアを決定するために使用される一部の要素は使用されます。また、それでも、Lighthouseなどの合成テストを使用して測定されるのではなく、Chrome User Experience Report(CRUX)の実際のフィールドデータを使用して測定されます。

Lighthouse 6.0の新しい指標:TBT、LCP、CLS

Lighthouse v6.0では、いくつかの新しい知覚速度メトリクスが導入され、全体的なメトリクスがページのLighthouseパフォーマンススコアに与える影響が再定式化されています。

灯台5.7 重量 Lighthouse 6.0 重量
最初の満足塗料(FCP) 20% 最初の満足塗料(FCP) 15%
速度インデックス(SI) 27% 速度インデックス(SI) 15%
最初の意味のあるペイント(FMP) 7% 最大コンテンションペイント(LCP) 25%
最初のCPUアイドル(FCI) 13% 総ブロック時間(TBT) 15%
対話時間(TTI) 33% 対話時間(TTI) 15%
- - 累積レイアウトシフト数(CLS) 5%

総ブロック時間(TBT)

総ブロック時間はLighthouse v6.0に含まれる新しいメトリックで、ページの読み込み中にJavaScriptの解析と実行がメインスレッドをブロックする時間を測定します。 このメトリックは、最新のJavaScriptを多用するSPA/PWAを非常に厳しく扱います。 React Storefront 7がバンドルサイズを50%削減したにもかかわらず、20は製品ページのLighthouse v6.0パフォーマンススコアで30ポイント低下しました。これは主に、全体的なパフォーマンススコアの25%に影響を与える指標としてTBTが含まれているため です。

サーバーサイドレンダリングをサポートするNext.jsのような同型フレームワークを使用している場合、TBTは主にバンドルサイズとハイドレーション時間によって決まります。 簡単に言うと、TBTを改善する唯一の方法は、依存関係を削除したり、コンポーネントを最適化したり、コンポーネントを減らしてサイトを簡素化したりすることです。

最大コンテンツ表示時間(LCP)

LCPは、Lighthouse v6.0の総合スコアよりも25%の重みを持つ新しい指標です。 LCPは、最大のコンテンツ要素がペイントを完了するまでにかかる時間を観察することで、ユーザーが最初のページ読み込みパフォーマンスをどのように認識しているかを定量化することを目的としています。 ほとんどのサイト、特にeコマースWebサイトでは、最大のコンテンツ要素はヒーローイメージです。 React Storefrontアプリの製品ページの場合、最大のコンテンツ要素はメインの製品イメージです。 これがあなたの場所にあるどの要素かわからない場合、 PSIは あなたに言う:

LCP用に最適化するには、イメージができるだけ早くロードされるようにする必要があります。

累積レイアウトシフト数(CLS)

累積レイアウトシフトは、最初のページ読み込み時にページレイアウトがどれだけシフトするかを測定します。 レイアウトシフトは、最も一般的には画像によって引き起こされます。ネットワークからデータがダウンロードされると、画像に合わせてサイズが変更されるため、画像の周囲に要素が押し込まれる傾向があります。 レイアウトのずれは、ロード前に各画像のスペースを確保することで完全に解消できます。 幸いなことに、React StorefrontのImageコンポーネントはすでにこれを行っているため、React Storefrontスターターアプリは、0の完璧なCLSスコアを誇っています。

貧しいCLSの他の一般的な犯人は、ページが最初に描かれた後に表示されるバナーとポップアップであることに注意してください。 ユーザーはそれらを嫌っており、現在はLighthouseもそうです。

Lighthouse v6.0用にReact Storefrontを最適化する方法

PageSpeed Insightsを使用してLighthouse v6.0でReact Storefrontスターターアプリの製品ページを最初にテストしたとき、 60秒以下のスコアでした。

スコアを上げるために、まずLCPに目を向けました。 2.5秒の時点では、FCPは非常に高いレベルでしたが(後で説明します)、FCPとLCPの間の3秒近くのギャップは、改善が必要なものとして際立っていました。

LCPの最適化

React Storefrontのメイン製品イメージ(単にプレースホルダー、中央に「Product 1」がある緑のボックス)は、https://via.placeholder.comを指すsrc URLを持つHTML imgタグとしてレンダリングされました。 そのサイトにはまともなTTFB (約150ms)があり、Layer0 CDN-as-JavaScriptのようなより高速なCDNに移行することで改善できる可能性があります。 それでも、150ミリ秒がFCPとLCPの間の3秒近くのギャップを占めているかどうかは疑問でした。 ギャップを解消するために、次のようなbase64データURLを使用して画像をインライン展開しました。

				
					<img decoding="async" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAA…"/>

				
			

これを行うには、メインイメージをダウンロードしてbase64に変換し、Layer0のServerless JavaScriptクラウドでのサーバーサイドレンダリング中にsrc属性に詰め込みます。 product APIエンドポイントの行は次のとおりです。

				
					const mainProductImage = result.product.media.full[0]
mainProductImage.src = await getBase64ForImage(mainProductImage.src)
				
			

画像を取得してbase 64に変換するコードを次に示します。

				
					import fetch from 'node-fetch'

export default async function getBase64ForImage(src) {
  const res = await fetch(src)
  const contentType = res.headers.get('content-type')
  const buffer = await res.buffer()
  return `data:${contentType};base64,${buffer.toString('base64')}`
return `data:${contentType};base64,${buffer.toString('base64')}` return `data:${contentType};base64,${buffer.toString('base64')}` return `data:${contentType};base64,${buffer.toString('base64')}`
}

				
			

かなりシンプルで古い学校。 スコアへの影響は?

LCPを5.3秒から2.8秒に落とすことで、ページのLighthouse v6.0スコアで21ポイントを獲得しました。 このような小さな変更がLighthouse v6.0のスコアに劇的な影響を与える可能性があることは少し不安ですが、それを受け入れます。 すべての指標はランによって多少異なるが、全体のスコアは一貫して低い80年代にあったことに注意してください。 v6.0で最もパフォーマンスの高い主要なeコマースWebサイトは、PSIで測定された87をスコアしており、90年代からすぐにわかるように見えます。www.rockauto.comをご覧ください

上に示したFCPとLCPのギャップは、いくつかの実行で見たのと同じくらい大きくなりました。 ほとんどの場合、ギャップは100ms~300msの範囲にあります。 FCPとLCPが同じであることもあった。

TBTの最適化

次に、TBTの改善を試みました。 これは非常に困難でした。 前述したように、LCPを改善するには、JavaScriptバンドルのサイズを小さくするか、ハイドレーション時間を改善する必要があります。 ほとんどのアプリでは、バンドルを小さくするために依存関係を削除し始めるのは簡単ではありません。 React Storefront 7で構築されたアプリは、Next.jsのWebpack構成によって提供されるバンドルサイズを最小限に抑える多くのコンパイル時最適化と、Material UIのためのBabel最適化の恩恵をすでに受けて います。 ではどこで改善できるでしょうか? 水分補給時間

Lazy Hydration for the Win!

幸いなことに、React Storefrontコミュニティは、Lighthouse v6.0がリリースされる前に、怠惰な水分補給をサポートする作業をすでに開始していました。 これは確かに私たちの取り組みを加速させました。

気づいていない場合、ハイドレーションとは、Reactがサーバー上でレンダリングされたHTML要素を制御してインタラクティブになることを指します。 ボタンはクリック可能になり、カルーセルはスワイプ可能になります。 ページに含まれるコンポーネントが多いほど、水分補給にかかる時間は長くなります。 メインメニューや製品イメージカルーセルなどの複雑なコンポーネントは、さらに時間がかかります。

怠惰な水和は、最初のページロードの後(およびTBTが計算された後)、絶対に必要で最も重要なことに、特定のコンポーネントの水和を遅らせることを伴います。 怠惰な水分補給は危険です。 ユーザーがページ要素と対話する前に、ページ要素がユーザー入力に応答できる状態になっていることを確認する必要があります。

React Storefrontで遅延ハイドレーションを実装することは、Material UIがコンポーネントがハイドレーションされた後にのみドキュメントにスタイルを動的に追加するCSS-in-JSに依存しているため、非常に困難であることが証明されました。 詳細は別の時間に保存します。 最終的には、開発者がコンポーネントツリー内の任意の場所に挿入して、ビューポートに要素をスクロールしたり、ユーザーが画面にタッチしたりするなど、特定のイベントが発生するまで水和を遅らせることができるLazyHydrateコンポーネントを構築しました。

ここでは、主要製品画像を表示するMediaCarouselを遅延水和しています。

				
					 import LazyHydrate from 'react-storefront/LazyHydrate'
<LazyHydrate id="carousel" on="touch">
  <MediaCarousel
    ...
  />
</LazyHydrate> 

				
			

アプリケーションのいくつかの領域に怠惰な水和を適用しました。特に、次のようなことが顕著です。

  • スライドインメニュー:ユーザーがハンバーガーのボタンをタップすると、これを水和します。
  • 折りの下にあるすべてのコントロール:サイズ、色、数量のセレクター、および商品情報タブが含まれます。
  • メインイメージカルーセル:これとメインメニューはおそらく最も機能的なコンポーネントであり、したがって水和物に最も高価です。

Lighthouse v6.0のスコアには、怠惰な水分補給が適用されています。

怠惰な水分補給はTBTを40%近く削減し、TTI(v6.0のスコアより15%のウェイト)を700ms削減した。 これにより、Lighthouse v6.0の総合スコアで6ポイントの上昇を記録しました。

FCPは少し上昇したが、LCPは低下したことに気付くでしょう。 これらの小さな変更は、基本的にPageSpeed Insightsを実行するときに発生する「ノイズの中」にあります。 すべてのスコアはラン間でわずかに変動します。

FCPの最適化

上記のスコアから、FCPやLCPはさらに改善される可能性があると感じました。 スクリプトはレンダリングをブロックできることがわかっているので、Next.jsがどのようにスクリプトをドキュメントにインポートするかを見てみました。

				
					<scriptsrc="/_next/static/runtime/main-cb5fd281935517c43f30.js"async=""></script>
				
			

ここでasyncを使用するのは最良の選択ではないかもしれません。 レンダリング中にスクリプトがダウンロードされると、スクリプトの評価中にレンダリングが一時停止することがあり、FCPとLCPの両方の時間が長くなります。 asyncの代わりにdeferを使用すると、スクリプトはドキュメントがペイントされた後にのみ評価されます。

残念ながら、Next.jsではスクリプトのインポート方法を変更できないため、NextScriptコンポーネントを次のように拡張する必要がありました。

				
					import React from 'react'
import { NextScript as OriginalNextScript } from 'next/document'
/**
 * A replacement for NextScript from `next/document` that gives you greater control over how script elements are rendered.
 * This should be used in the body of `pages/_document.js` in place of `NextScript`.
 */
export default class NextScript extends OriginalNextScript {
  static propTypes = {
    /**
     * Set to `defer` to use `defer` instead of `async` when rendering script elements.
     */
    mode: PropTypes.oneOf(['async', 'defer']),
  }
  static defaultProps = {
    mode: 'async',
  }
  getScripts() {
    return super.getScripts().map(script => {
      return React.cloneElement(script, {
        key: script.props.src,
        defer: this.props.mode === 'defer' ? true : undefined,
        async: this.props.mode === 'async' ? true : undefined,
      })
    })
  }
}

				
			

次に、pages/_document.jsに以下を追加しました。

				
					 <NextScript mode="defer" />
				
			

幸いなことに、これによりLCPと全体的なスコアが向上しました。

また、多くの実行でFCPをわずかに上回っていますが、これは「ノイズ」の範囲内にある可能性があります。 それにもかかわらず、DEFER vs asyncを使用した場合、全体のスコアは一貫して2〜3ポイント高くなりました。

合計

2020年5月下旬にLighthouse v6.0がリリースされたとき、React Storefrontアプリを含む多くのサイトのパフォーマンススコアが急落しました。 最適化が行われる前は、React StorefrontスターターアプリのPDPパフォーマンスは60年代後半に低下していました。 これらの最適化により、今では珍しくなっている90年代の低さを実現しました。 現時点では、スコアをさらに向上させる唯一の方法は依存関係を削除することであると考えています。これは、開発者の生産性とアプリケーションのパフォーマンスをトレードオフすることを意味する可能性があります。

それはまた別の機会に議論します。 私たちが試したことがうまくいかなかったことをいくつかお話ししましょう。

プレアクト

Preactはバンドルサイズを20 ~ 30%小さくしますが、Lighthouse v6.0のスコアはTTIを含むすべてのメトリックで一貫して悪化していました。 理由はわかりませんが、これはLighthouse v6.0に新しくも限定されたものでもないことはわかっています。 Lighthouse v5.7でも遅くなりました。 私たちは定期的にチェックインを続けており、いつかこれが修正されることを願っています。

チャンキング

Next.jsは最近、ブラウザアセットのよりきめ細かいチャンクを導入しました。 これがNext.js 9.1で初めて導入されたとき、小さなチャンクが追加されたことで、実際にTTIが悪化することに気づきました。 新しいバージョンがリリースされた後にアプリをリピーターするユーザーにとって、ブラウザキャッシュをより効果的に活用できるため、おそらくアプリをより速くするでしょうが、Lighthouseはそのいずれも気にしません。 そのため、React Storefrontでは、ブラウザーのチャンク数をしばらく1つに制限しています。

				
					const webpack = require('webpack')
const withReactStorefront = require('react-storefront/plugins/withReactStorefront')
module.exports = withReactStorefront({
  target: 'serverless',
  webpack: config => {
    config.plugins.push(
      new webpack.optimize.LimitChunkCountPlugin({
        maxChunks: 1,
      })
    )
    return config
  },
})

				
			

Webフォント

ほとんどのサイトでは、カスタムWebフォントを使用しています。 デフォルトでは、React StorefrontはRobotoを使用します(ただし、これは変更または削除できます)。 カスタムWebフォントは、プレーンでシンプルなパフォーマンスを殺します。 Webフォントを削除すると、1 Second of FCPについて学ぶことができます。

分析と同様に、利害関係者は特定のフォントを持つためにパフォーマンスをトレードオフしようとしているようです。 React Storefrontはサイトと同じオリジンのWebフォントを提供し、Google FontsなどのサードパーティCDNからフォントをロードした場合に発生するTLSネゴシエーション時間を排除します。 具体的には、NPMのtypeface-Robotoパッケージを使用しています。 WebpackのCSS-loaderと組み合わせると、typeface-Robotoを使用すると、ブラウザがダウンロードして解析する必要がある別のCSSファイルを介してフォントがインポートされます。 CSSをドキュメントにインライン化するとパフォーマンスが向上すると考えましたが、そうではありませんでした。

パフォーマンスに関しては、常に測定する必要があります。 理論的に動作する必要がある最適化は、実際には実行されない場合があります。

灯台のスコアを上げる

当社のお客様は、Lighthouse v6.0の上位500 eコマースWebサイトの95パーセンタイルにランクされています。

コンサルティング会話のスケジュールを設定します。