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

パフォーマンス データにデバッグ情報を関連付けて、分析で実際のユーザーの問題を特定して修正する方法について学びます。

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 には、利用可能な場合はフィールドデータとラボデータの両方が表示されます。これらのデータは異なる場合があります

ラボ(Lighthouse)で報告される CLS の値と、フィールド(CrUX データ)で報告される CLS の値は大きく異なります。これは、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 候補要素を特定するには、LCP 時間値の特定に使用する Largest Contentful Paint 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);
onINP(sendToGoogleAnalytics);

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

このコードは、単一のデバッグ シグナルを報告する方法も示していますが、指標ごとに複数の異なるシグナルを収集して報告できるようにすると便利です。

たとえば、INP をデバッグするには、操作対象の要素、操作の種類、時刻、loadState、操作フェーズなどを収集する必要があります(長いアニメーション フレームのデータなど)。

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

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);
onINP(sendToGoogleAnalytics);

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

データをレポートして可視化する

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

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

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

概要

この投稿では、既存のパフォーマンス API と web-vitals ライブラリを使用してデバッグ情報を取得し、現場での実際のユーザーのアクセスに基づいてパフォーマンスを診断する具体的な方法について説明しました。このガイドではウェブに関する主な指標に焦点を当てていますが、ここで説明するコンセプトは、JavaScript で測定可能なパフォーマンス指標のデバッグにも適用されます。

パフォーマンスの測定を初めて行う場合で、すでに Google アナリティクスをご利用の場合は、ウェブバイタル レポート ツールから始めることをおすすめします。このツールは、Core Web Vitals 指標のデバッグ情報のレポートをすでにサポートしています。

アナリティクス ベンダーとしてプロダクトの改善を図り、ユーザーに詳細なデバッグ情報を提供したいとお考えの場合は、ここで説明する手法のいくつかを検討してください。ただし、ここで紹介するアイデアに限定しないでください。この記事は、すべてのアナリティクス ツールに一般的に適用することを目的としています。ただし、個々のアナリティクス ツールでは、さらに多くのデバッグ情報をキャプチャして報告できる可能性があります(また、そうすべきです)。

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