現場でパフォーマンスをデバッグ

パフォーマンス データをデバッグ情報に結び付ける方法を学び、アナリティクスで実際のユーザーの問題を特定して修正する方法を学びます。

Google では、パフォーマンスの測定とデバッグのためのツールとして、次の 2 つのカテゴリを提供しています。

  • ラボツール: さまざまな条件(低速なネットワークやローエンドのモバイル デバイスなど)を再現できるシミュレーション環境でページが読み込まれる Lighthouse などのツールです。
  • フィールド ツール: Chrome から集計された実際のユーザーデータに基づく Chrome ユーザー エクスペリエンス レポート(CrUX)などのツール。(PageSpeed InsightsSearch Console などのツールによってレポートされるフィールド データは、CrUX データから収集されたものです)。

フィールド ツールはより正確なデータ(実際に実際のユーザー エクスペリエンスを表すデータ)を提供しますが、ラボツールは問題の特定と修正に優れています。

CrUX データはページの実際のパフォーマンスをより正確に表しますが、CrUX スコアがわかっていても、パフォーマンスを改善する方法を見つけるには役立ちません。

Lighthouse では問題を特定し、具体的な改善案を提示します。ただし、Lighthouse で提案されるのは、ページの読み込み時に検出されたパフォーマンスの問題のみです。ページのスクロールやボタンのクリックなどのユーザー操作の結果としてのみ発生する問題は検出されません。

このことから、Core Web Vitals やその他のパフォーマンス指標のデバッグ情報を実際のユーザーから入手するにはどうすればよいかという重要な問題が発生します。

この投稿では、現在の Core Web Vitals の各指標に関する追加のデバッグ情報を収集するために使用できる API について詳しく説明します。また、既存の分析ツールでこのデータをキャプチャするためのアイデアも提供します。

アトリビューションとデバッグ用の API

Cumulative Layout Shift(CLS)

Core Web Vitals のすべての指標の中で、CLS はおそらく、現場のデバッグ情報の収集が最も重要となる指標でしょう。CLS はページ全体を通して測定されるため、ユーザーによるページ操作方法(スクロール距離、クリックなど)が、レイアウト シフトの有無やシフトする要素に大きな影響を与える可能性があります。

PageSpeed Insights の以下のレポートについて考えてみましょう。

さまざまな CLS 値を含む PageSpeed Insights レポート
PageSpeed Insights では、可能な場合はフィールド データとラボデータの両方が表示されます。表示されるデータは異なる場合があります

ラボの CLS についてレポートされる値(Lighthouse)と、フィールドの CLS の値(CrUX データ)はかなり異なるため、Lighthouse でのテストでは使用されないインタラクティブなコンテンツがページ内にたくさんあることを考えると、これは当然のことです。

ただし、ユーザー インタラクションがフィールド データに影響することがわかっていても、ページのどの要素がシフトしているかを把握して、75 パーセンタイルのスコアが 0.28 になるようにする必要があります。これは LayoutShiftAttribution インターフェースによって可能になります。

レイアウト シフトのアトリビューションを取得する

LayoutShiftAttribution インターフェースは、Layout Instability API が出力する各 layout-shift エントリで公開されます。

両方のインターフェースの詳細については、レイアウト シフトをデバッグするをご覧ください。この投稿で知っておくべき主なことは、デベロッパーは、ページ上で発生するすべてのレイアウト シフトと、シフトされる要素を監視できることです。

次のサンプルコードは、各レイアウト シフトとシフトした要素をログに記録しています。

new PerformanceObserver((list) => {
  for (const {value, startTime, sources} of list.getEntries()) {
    // Log the shift amount and other entry info.
    console.log('Layout shift:', {value, startTime});
    if (sources) {
      for (const {node, curRect, prevRect} of sources) {
        // Log the elements that shifted.
        console.log('  Shift source:', node, {curRect, prevRect});
      }
    }
  }
}).observe({type: 'layout-shift', buffered: true});

レイアウト シフトが発生するたびにデータを測定して分析ツールに送信するのは、おそらく実用的ではありません。ただし、すべてのシフトをモニタリングすることで、最悪のシフトを追跡し、その情報のみを報告できます。

目標は、すべてのユーザーで発生するすべてのレイアウト シフトを特定して修正することではありません。目標は、最も多くのユーザー数に影響を与え、75 パーセンタイルでページの CLS に最も大きく寄与するシフトを特定することです。

また、シフトが発生するたびに最大のソース要素を計算する必要はなく、CLS 値を分析ツールに送信する準備ができた場合にのみ計算する必要があります。

次のコードは、CLS の原因となった layout-shift エントリのリストを取得し、最大のシフトから最大のソース要素を返します。

function getCLSDebugTarget(entries) {
  const largestEntry = entries.reduce((a, b) => {
    return a && a.value > b.value ? a : b;
  });
  if (largestEntry && largestEntry.sources && largestEntry.sources.length) {
    const largestSource = largestEntry.sources.reduce((a, b) => {
      return a.node && a.previousRect.width * a.previousRect.height >
          b.previousRect.width * b.previousRect.height ? a : b;
    });
    if (largestSource) {
      return largestSource.node;
    }
  }
}

最大の変化に大きく貢献している要素を特定したら、それを分析ツールに報告できます。

あるページで CLS に最も大きく寄与している要素はユーザーによって異なる可能性がありますが、すべてのユーザーからこれらの要素を集約すると、最も多くのユーザーに影響を与える要素のリストを生成できます。

これらの要素のシフトの根本原因を特定して修正すると、小さなシフトがページの「最悪の」シフトとしてアナリティクス コードでレポートされるようになります。最終的には、レポートされる変化はすべて小さくなり、ページが「良好」しきい値の 0.1 に十分収まるようになります。

最大のシフトソース要素とともにキャプチャすると役立つ可能性のあるその他のメタデータは次のとおりです。

  • 最大シフトの時刻
  • 最大シフト時の URL パス(シングルページ アプリケーションなど、URL を動的に更新するサイトの場合)。

Largest Contentful Paint(LCP)

該当フィールドの LCP をデバッグするには、まずどの要素がそのページの読み込みで最大の要素(LCP 候補要素)であるかを把握する必要があります。

なお、LCP 候補要素は、まったく同じページであってもユーザーごとに異なる可能性があります(実際はごく一般的なことです)。

この問題は次のような原因で発生することがあります。

  • ユーザー デバイスの画面解像度が異なると、ページ レイアウトも異なるため、異なる要素がビューポート内に表示されます。
  • ユーザーは必ずしも最上部にスクロールしたページを読み込むとは限りません。多くの場合、リンクにはフラグメント識別子テキスト フラグメントが含まれます。つまり、ページのどのスクロール位置でもページが読み込まれ、表示される可能性があります。
  • コンテンツは現在のユーザーに合わせてパーソナライズされる場合があるため、LCP の候補要素はユーザーごとに大きく異なる可能性があります。

つまり、どの要素または要素のセットが、特定のページで最も一般的な LCP 候補要素になるかを予測することはできません。実際のユーザーの行動に基づいて測定する必要があります。

LCP 候補要素を特定する

JavaScript で LCP 候補要素を特定するには、Largest Contentful Paint API を使用します。これは、LCP 時間値の特定に使用するのと同じ API です。

largest-contentful-paint エントリを監視している場合、最後のエントリの element プロパティを調べることで、現在の LCP 候補要素を特定できます。

new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];

  console.log('LCP element:', lastEntry.element);
}).observe({type: 'largest-contentful-paint', buffered: true});

LCP 候補要素を特定したら、それを指標値とともに分析ツールに送信できます。CLS と同様に、これは最初に最適化すべき最も重要な要素を特定するのに役立ちます。

LCP 候補要素に加えて、LCP サブパート時間を測定すると、サイトに関連する具体的な最適化手順を判断するのに役立ちます。

Interaction to Next Paint(INP)

INP のフィールドで収集する最も重要な情報は次のとおりです。

  1. 操作された要素
  2. そのインタラクションの種類
  3. そのやり取りが行われた日時

インタラクションが遅い主な原因は、メインスレッドがブロックされることです。これは、JavaScript の読み込み中によく発生します。最も遅いインタラクションがページの読み込み中に発生しているかどうかを知ることは、問題解決のために何を行う必要があるかを判断する際に役立ちます。

INP 指標は、インタラクションの完全なレイテンシ(登録済みのイベント リスナーの実行にかかる時間や、すべてのイベント リスナーの実行後に次のフレームの描画にかかった時間など)を考慮します。つまり、INP にとって、インタラクションが遅くなる傾向があるターゲット要素と、そのインタラクションの種類を知ることは非常に重要です。

次のコードは、INP エントリのターゲット要素と時刻をログに記録します。

function logINPDebugInfo(inpEntry) {
  console.log('INP target element:', inpEntry.target);
  console.log('INP interaction type:', inpEntry.name);
  console.log('INP time:', inpEntry.startTime);
}

なお、このコードでは、どの event エントリが INP エントリかを判断する方法は示されていません。このロジックはより複雑なためです。次のセクションでは、web-vitals JavaScript ライブラリを使用してこの情報を取得する方法について説明します。

web-vitals JavaScript ライブラリの使用

前のセクションでは、分析ツールに送信するデータに含めるデバッグ情報を取得するための一般的な推奨事項とコードサンプルを紹介しました。

バージョン 3 以降、web-vitals JavaScript ライブラリには、これらの情報をすべて表面化させるアトリビューション ビルドと、いくつかの追加シグナルが含まれています。

パフォーマンスの問題の根本原因の特定に役立つデバッグ文字列を含む追加のイベント パラメータ(またはカスタム ディメンション)を設定する方法を、次のコード例に示します。

import {onCLS, onINP, onLCP} from 'web-vitals/attribution';

function sendToGoogleAnalytics({name, value, id, attribution}) {
  const eventParams = {
    metric_value: value,
    metric_id: id,
  }

  switch (name) {
    case 'CLS':
      eventParams.debug_target = attribution.largestShiftTarget;
      break;
    case 'LCP':
      eventParams.debug_target = attribution.element;
      break;
    case 'INP':
      eventParams.debug_target = attribution.interactionTarget;
      break;
  }

  // Assumes the global `gtag()` function exists, see:
  // https://developers.google.com/analytics/devguides/collection/ga4
  gtag('event', name, eventParams);
}

onCLS(sendToGoogleAnalytics);
onLCP(sendToGoogleAnalytics);
onFID(sendToGoogleAnalytics);
onINP(sendToGoogleAnalytics);

このコードは Google アナリティクスに固有のものですが、基本的な考え方は他の分析ツールにも応用できるはずです。

このコードでは、1 つのデバッグ シグナルに関するレポートを作成する方法も示されていますが、指標ごとに複数の異なるシグナルを収集してレポートできると便利です。

たとえば、INP をデバッグするには、操作している要素、操作タイプ、時間、loadState、操作フェーズなど(Long Animation Frame Data など)を収集します。

次の例の INP に示すように、web-vitals アトリビューション ビルドは追加のアトリビューション情報を公開します。

import {onCLS, onINP, onLCP} from 'web-vitals/attribution';

function sendToGoogleAnalytics({name, value, id, attribution}) {
  const eventParams = {
    metric_value: value,
    metric_id: id,
  }

  switch (name) {
    case 'INP':
      eventParams.debug_target = attribution.interactionTarget;
      eventParams.debug_type = attribution.interactionType;
      eventParams.debug_time = attribution.interactionTime;
      eventParams.debug_load_state = attribution.loadState;
      eventParams.debug_interaction_delay = Math.round(attribution.inputDelay);
      eventParams.debug_processing_duration = Math.round(attribution.processingDuration);
      eventParams.debug_presentation_delay =  Math.round(attribution.presentationDelay);
      break;

    // Additional metric logic...
  }

  // Assumes the global `gtag()` function exists, see:
  // https://developers.google.com/analytics/devguides/collection/ga4
  gtag('event', name, eventParams);
}

onCLS(sendToGoogleAnalytics);
onLCP(sendToGoogleAnalytics);
onFID(sendToGoogleAnalytics);
onINP(sendToGoogleAnalytics);

公開されているデバッグ シグナルの完全なリストについては、web-vitals アトリビューションのドキュメントをご覧ください。

データのレポートと可視化

デバッグ情報と指標値の収集を開始したら、次に、すべてのユーザーのデータを集計して、パターンと傾向を探します。

前述のように、ユーザーが直面しているすべての問題に必ずしも対処する必要はありません。最も多くのユーザーに影響を与えている問題、特に最初に対処すべき問題です。これらは Core Web Vitals のスコアに最大の悪影響を及ぼす問題でもあるはずです。

GA4 については、BigQuery を使用してデータをクエリして可視化する方法に関する専用記事をご覧ください。

まとめ

この投稿が、既存のパフォーマンス API と web-vitals ライブラリを使用してデバッグ情報を取得し、実際のユーザーが実際に訪れた場所に基づいてパフォーマンスを診断する方法の概要を説明するのに役立ちましたら幸いです。このガイドでは Core Web Vitals に焦点を当てていますが、このコンセプトは JavaScript で測定可能なパフォーマンス指標のデバッグにも当てはまります。

パフォーマンスの測定を始めたばかりで、すでに Google アナリティクスを使用している場合は、Core Web Vitals 指標のデバッグ情報の報告をすでにサポートしている Web Vitals レポートツールの使用をおすすめします。

分析ベンダーの方は、サービスを改善し、より多くのデバッグ情報をユーザーに届けたいとお考えの場合に、ここで説明する手法の使用を検討してください。ただし、ここで紹介するアイデアだけにとどまらないでください。この投稿は、すべての分析ツールに一般的に適用できるようにすることを想定しています。ただし、個々の分析ツールでさらに多くのデバッグ情報を取得して報告できる可能性があります(また、そのようにする必要があります)。

最後に、API 自体の機能または情報が不足しているために、これらの指標をデバッグする能力にギャップがあると思われる場合は、web-vitals-feedback@googlegroups.com にフィードバックをお送りください。