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

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

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

  • ラボツール: Lighthouse などのツール。ページがさまざまな条件(低速なネットワークやローエンドのモバイル デバイスなど)を模倣できるシミュレートされた環境で読み込まれます。
  • フィールド ツール: Chrome の実際のユーザーデータに基づいて集計された Chrome ユーザー エクスペリエンス レポート(CrUX)などのツール。(PageSpeed Insights や Search 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);

公開されるデバッグ シグナルの完全なリストについては、ウェブに関する指標の属性に関するドキュメントをご覧ください。

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

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

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

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

概要

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

アナリティクス ベンダーがプロダクトを改善し、ユーザーにデバッグ情報をより多く提供しようとする場合は、ここで説明する手法を検討してください。ただし、ここで紹介するアイデアだけに限定する必要はありません。この投稿は、すべての分析ツールに一般的に適用されることを目的としていますが、個々の分析ツールでは、さらに多くのデバッグ情報を取得してレポートできる(また、そうすべき)可能性があります。

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