Home Blogs Lighthouse v6.0用にウェブサイトを最適化する
Applications

Lighthouse v6.0用にウェブサイトを最適化する

About The Author

Outline

Layer0はオープンソースのeコマースPWAフレームワークReact Storefrontの主要な貢献者である。 今年の初めには、多くの新機能と最適化を React Storefront v7最も重要なのはNext.jsへの移行と、useStateフックやcontext APIなどのReactの新しい組み込み機能を利用して、いくつかの鍵依存関係(例えばMobX )を削除したことである。 その結果、ブラウザのバンドルサイズはほぼ半分に縮小された。

これは当時としては素晴らしい成長であり、PageSpeed Insights(PSI)によって測定された典型的なReact StorefrontアプリのLighthouse (v5.7)のパフォーマンススコアを80年代から90年代に押し上げるのに役立った。 コンテキストに関しては、83以上のスコアがLighthouse v5.7の上位500のeコマースWebサイトの99%を上回った。 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 重量
First Contentful Paint(FCP)(ファースト・コンテンツフル・ペイント) 20% First Contentful Paint(FCP)(ファースト・コンテンツフル・ペイント) 15%
スピードインデックス(SI) 27% スピードインデックス(SI) 15%
最初の有意義なペイント(FMP) 7% 最大コンテンフルペイント(LCP) 25%
最初のCPUアイドル(FCI) 13% 合計ブロック時間(TBT) 15%
インタラクティブになるまでの時間(TTI) 33% インタラクティブになるまでの時間(TTI) 15%
- - 累積レイアウトシフト数(CLS) 5%

合計ブロック時間(TBT)

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

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

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

LCPは、Lighthouse v6.0のスコア全体に対して25%の重みを持つ新しいメトリックである。 LCPの目的は、ペインティングを完了するのに最大のコンテンツ要素がどれだけ時間を要するかを観察することによって、ユーザーが最初のページ読み込みパフォーマンスをどのように知覚するかを定量化することである。 ほとんどのサイト、特にeコマースのウェブサイトでは、最大の満足要素はヒーローイメージである。 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に移行することで改善できる。 しかし、FCPとLCPの間の3秒近くのギャップは150msであるとは考えられなかった。 ギャップを解消するために、次のようなbase64データURLを使用して画像をインライン展開した。

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

				
			

これは、メインイメージをダウンロードしてbase64に変換し、Layer0のServerless JavaScriptクラウド上でのサーバーサイドレンダリング中にsrc属性に詰め込むことで実現した。 製品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年代であることに注意する必要がある。 コンテキストについては、PSIで測定したv6.0のスコア87で最もパフォーマンスの高い大手eコマースWebサイトが90年代からまっすぐに見える- www.rockauto.comを見てみよう

上に示したFCPとLCPのギャップは、数回のランで見たのとほぼ同じくらい大きかった。 ほとんどの場合、ギャップは100msから300msの範囲にあった。 FCPとLCPが同じ場合もある。

TBTを最適化する

次に、TBTの改善を試みた。 これはかなり挑戦的だった。 前述したように、LCPを改善するには、JavaScriptバンドルのサイズを小さくするか、ハイドレーション時間を改善する必要がある。 ほとんどのアプリでは、バンドルを小さくするために依存関係を取り除き始めることは単純に不可能である。 React Storefront 7でビルドされたアプリは、Next.jsのwebpack設定によって提供されるバンドルサイズを最小限に抑える多くのコンパイル時最適化や、Material UIのための特定のBabel最適化の恩恵をすでに受けている。 ではどこを改善すればいいのか? 水分補給時間。

レイジーハイドレーションで勝利!

幸いにも、React StorefrontコミュニティはLighthouse v6.0が廃止される前に、レイジーハイドレーションのサポートに取り組んでいた。 これは確かに我々の努力を加速させた。

知らない場合には、HYDRATIONとは、Reactがサーバー上でレンダリングされたHTML要素を制御して、それらがインタラクティブになることを指す。 ボタンがクリック可能になり、カルーセルがスイープ可能になるなど。 ページの構成要素が多ければ多いほど、水和にかかる時間は長くなる。 メインメニューや製品イメージカルーセルなどの複雑なコンポーネントは、さらに時間がかかる。

遅延水和は、それが絶対に必要であり、最も重要なことに、最初のページロードの後(そしてTBTが計算された後)に特定の成分の水和を遅らせることを伴う。 遅延水和は危険である場合がある。 ユーザーがページ要素を操作しようとする前に、ページ要素がユーザー入力に応答する準備ができていることを確認する必要がある。

React Storefrontで遅延水和を実装するのは、Material UIがCSS-in-JSに依存しているため非常に困難であることが判明した。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にわずかにぶつかったが、これは「ノイズ」の範囲内かもしれない。 それでも、ディフェアーと非同期を使用した場合の総合スコアは一貫して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
  },
})

				
			

ウェブフォント

ほとんどの場所はカスタムの網のフォントを使用する。 デフォルトではReact StorefrontはRobotoを使用する(ただし、これは変更または削除することができる)。 カスタムWebフォントは、シンプルでシンプルなパフォーマンスを損なう。 Webフォントを削除すると、FCPの約1秒が得られる。

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

パフォーマンスに関しては、常に測定する必要がある。 理論上うまくいくべき最適化は実際には行われていないかもしれない。

Lighthouseのスコアを上げる

当社の顧客は、Lighthouse v6.0のEコマースWebサイトのトップ500の95パーセンタイルにランクされている。

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