इंटरैक्शन और सॉफ़्ट-नेविगेशन पेंट के समय को मेज़र करना

स्टैंडर्ड पेंट और रिस्पॉन्सिवनेस मेट्रिक, शुरुआती पेज लोड होने के बाद मेज़रमेंट गैप छोड़ देती हैं:

  • LargestContentfulPaint (largest-contentful-paint) सिर्फ़ शुरुआती ("हार्ड") दस्तावेज़ लोड को मेज़र करता है. साथ ही, उपयोगकर्ता के पेज से पहली बार इंटरैक्ट करते ही एंट्री दिखाना बंद कर देता है.
  • PerformanceEventTiming और event / पेज के रिस्पॉन्स में लगने वाला समय (आईएनपी), अगले फ़्रेम (जैसे, बटन की चालू स्थिति या लोडिंग स्पिनर) तक इनपुट रिस्पॉन्सिवनेस को मेज़र करता है. हालांकि, यह बाद के एसिंक्रोनस पेंट को ट्रैक नहीं करता है. जैसे, fetch() के रिज़ॉल्व होने के बाद रेंडर किया गया नया कॉन्टेंट या क्लाइंट-साइड रूट ट्रांज़िशन पूरा होना.
  • सिंगल पेज ऐप्लिकेशन (एसपीए) में रूट में होने वाले बदलावों को मॉनिटर करना, फ़्रेमवर्क कोड के बाहर मुश्किल हो सकता है. सिर्फ़ यूआरएल अपडेट पर नज़र रखने से, यूआरएल में हुए ऐसे बदलावों की ज़्यादा रिपोर्ट मिल सकती हैं जो रूट में हुए बदलावों से लिंक नहीं हैं. उदाहरण के लिए, ऐंकर लिंक में हुए बदलाव, किसी एक रूट में हुए बदलाव के लिए कई "रीडायरेक्ट".

InteractionContentfulPaint (interaction-contentful-paint) और PerformanceSoftNavigation (soft-navigation) परफ़ॉर्मेंस एंट्री टाइप, इस कमी को पूरा करते हैं. ये इंटरैक्शन के असर को एसिंक्रोनस टास्क (fetch, await, setTimeout) में लागू करते हैं, ताकि नए कॉन्टेंटफ़ुल पेंट और सिंगल-पेज ऐप्लिकेशन (एसपीए) रूट ट्रांज़िशन को सीधे तौर पर उस उपयोगकर्ता इंटरैक्शन से एट्रिब्यूट किया जा सके जिसने उन्हें ट्रिगर किया था.

इन्हें कैसे लागू करें

1. इंटरैक्शन से ट्रिगर होने वाले कॉन्टेंटफ़ुल पेंट पर नज़र रखना

type: 'interaction-contentful-paint' और buffered: true के साथ PerformanceObserver का इस्तेमाल करें, ताकि उपयोगकर्ता के किसी भी इंटरैक्शन की वजह से, पेज पर मौजूद सबसे बड़े कॉन्टेंट एलिमेंट को रेंडर होने में लगने वाले समय को देखा जा सके.

हर InteractionContentfulPaint एंट्री में यह जानकारी होती है:

  • interactionId: उपयोगकर्ता के इंटरैक्शन को शुरू करने वाला यूनीक आइडेंटिफ़ायर (PerformanceEventTiming.interactionId और PerformanceSoftNavigation.interactionId से मैच करने वाला).
  • startTime: वह टाइमस्टैंप जब उपयोगकर्ता ने इंटरैक्शन शुरू किया. यह टाइमस्टैंप, शुरुआती हार्ड नेविगेशन के टाइम ऑरिजिन के हिसाब से होता है.
  • paintTime और presentationTime: रेंडरिंग का फ़ेज़ कब खत्म हुआ और पेंट किए गए पिक्सल स्क्रीन पर कब दिखे.
  • duration: इंटरैक्शन शुरू होने से लेकर, पेंट प्रज़ेंटेशन तक बीता समय (presentationTime - startTime).
  • largestContentfulPaint: एक स्नैपशॉट LargestContentfulPaint ऑब्जेक्ट, जिसमें अब तक के इंटरैक्शन के लिए, सबसे बड़े पेंट किए गए एलिमेंट की जानकारी शामिल होती है (element, size, url, id, renderTime, loadTime).
/**
 * Observes contentful paints caused by user interactions and invokes a callback
 * whenever an interaction produces a larger contentful paint.
 *
 * @param {(entry: PerformanceEntry) => void} onInteractionPaint
 */
export function observeInteractionPaints(onInteractionPaint) {
  // Feature-detect support before calling observe() so unsupported browsers
  // do not throw or log console warnings.
  if (
    typeof PerformanceObserver === 'undefined' ||
    !PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint')
  ) {
    return null;
  }

  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // entry.duration is already calculated as (entry.presentationTime - entry.startTime),
      // giving the total time from the user interaction to this contentful paint.
      onInteractionPaint(entry);
    }
  });

  // MANDATORY: Pass buffered: true to capture interaction paints that occurred
  // before this observer was registered.
  observer.observe({ type: 'interaction-contentful-paint', buffered: true });
  return observer;
}

2. एसपीए सॉफ़्ट नेविगेशन के लिए, सबसे बड़े एलिमेंट को रेंडर करने में लगने वाले समय (एलसीपी) का आकलन करना

जब किसी भरोसेमंद उपयोगकर्ता के इंटरैक्शन की वजह से, यूआरएल में बदलाव और कॉन्टेंटफ़ुल पेंट, दोनों होते हैं, तो ब्राउज़र से सॉफ़्ट नेविगेशन का सिग्नल मिलता है. हर सॉफ़्ट नेविगेशन के लिए एलसीपी का हिसाब लगाने के लिए:

  1. buffered: true की मदद से, 'soft-navigation' और 'interaction-contentful-paint', दोनों एंट्री देखें.
  2. ज़रूरी: 'soft-navigation' एंट्री मिलने पर, softNavEntry.getLargestInteractionContentfulPaint() को कॉल करके, एलसीपी के शुरुआती कैंडिडेट को सीड करें. अगर ऐप्लिकेशन ने यूआरएल को अपडेट करने से पहले कॉन्टेंट को रेंडर किया है (उदाहरण के लिए, व्यू को रेंडर करना और फिर history.pushState() को कॉल करना), तो वे शुरुआती interaction-contentful-paint एंट्री, soft-navigation एंट्री से पहले भेजी गई थीं. साथ ही, उनमें अब भी पिछला navigationId मौजूद है.
  3. ज़रूरी: entry.interactionId === softNavEntry.interactionId को मैच करके, बाद की 'interaction-contentful-paint' एंट्री को चालू सॉफ़्ट नेविगेशन से कोरिलेट करें. entry.navigationId से मैच न करें. interactionId का इस्तेमाल करने से यह पक्का होता है कि यूआरएल अपडेट होने से पहले हुए पेंट शामिल किए जाएं. साथ ही, नए रूट पर बाद में हुए ऐसे इंटरैक्शन से पेंट को बाहर रखा जाए जो काम के नहीं हैं.
/**
 * Tracks FCP and LCP for each client-side soft navigation and reports finalized
 * route metrics when the user navigates to a new route or hides the page.
 *
 * @param {(report: {
 *   url: string,
 *   navigationId: number,
 *   interactionId: number,
 *   fcpDuration: number,
 *   lcpDuration: number | null,
 *   lcpElement: Element | null,
 *   lcpSize: number | null
 * }) => void} onSoftNavReport
 */
export function trackSoftNavigationLCP(onSoftNavReport) {
  const supported =
    typeof PerformanceObserver !== 'undefined' &&
    PerformanceObserver.supportedEntryTypes?.includes('soft-navigation') &&
    PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint');

  if (!supported) {
    return null;
  }

  let activeSoftNav = null;
  let activeLcpEntry = null;

  function finalizeCurrentSoftNav() {
    if (!activeSoftNav) return;

    onSoftNavReport({
      // activeSoftNav.name holds the destination URL of the soft navigation.
      url: activeSoftNav.name,
      navigationId: activeSoftNav.navigationId,
      interactionId: activeSoftNav.interactionId,
      // activeSoftNav.duration is (activeSoftNav.presentationTime - activeSoftNav.startTime),
      // which represents First Contentful Paint (FCP) relative to interaction start.
      fcpDuration: activeSoftNav.duration,
      // activeLcpEntry.duration is (activeLcpEntry.presentationTime - activeSoftNav.startTime),
      // representing the soft navigation's LCP duration relative to interaction start.
      lcpDuration: activeLcpEntry ? activeLcpEntry.duration : null,
      lcpElement: activeLcpEntry?.largestContentfulPaint?.element ?? null,
      lcpSize: activeLcpEntry?.largestContentfulPaint?.size ?? null,
    });

    activeSoftNav = null;
    activeLcpEntry = null;
  }

  const softNavObserver = new PerformanceObserver((list) => {
    for (const softNavEntry of list.getEntries()) {
      // Finalize the previous route's metrics before resetting state for the new route.
      finalizeCurrentSoftNav();

      activeSoftNav = softNavEntry;
      // MANDATORY: Seed the LCP candidate with getLargestInteractionContentfulPaint()
      // to capture any attributed contentful paints that occurred before the URL updated.
      activeLcpEntry = softNavEntry.getLargestInteractionContentfulPaint();
    }
  });

  const icpObserver = new PerformanceObserver((list) => {
    for (const icpEntry of list.getEntries()) {
      // MANDATORY: Match by interactionId (not navigationId) so only paints caused
      // by the soft navigation's initiating interaction update the route's LCP.
      if (activeSoftNav && icpEntry.interactionId === activeSoftNav.interactionId) {
        activeLcpEntry = icpEntry;
      }
    }
  });

  softNavObserver.observe({ type: 'soft-navigation', buffered: true });
  icpObserver.observe({ type: 'interaction-contentful-paint', buffered: true });

  // Finalize the active route's LCP when the page is backgrounded or unloaded.
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden') {
      finalizeCurrentSoftNav();
    }
  });

  return { softNavObserver, icpObserver, finalizeCurrentSoftNav };
}

3. web-vitals की मदद से, सॉफ़्ट नेविगेशन के दौरान Core Web Vitals को मेज़र करना

अगर आपका लक्ष्य, हार्ड और सॉफ़्ट नेविगेशन, दोनों के लिए स्टैंडर्ड Core Web Vitals (एलसीपी, सीएलएस, और आईएनपी) को किसी Analytics एंडपॉइंट पर रिपोर्ट करना है, तो हर मेट्रिक की टाइमलाइन को मैन्युअल तरीके से स्लाइस करने के बजाय, { reportSoftNavs: true } के साथ web-vitals लाइब्रेरी (v6.0.0+) का इस्तेमाल करें.

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

function sendToAnalytics(metric) {
  // Example-only endpoint; replace with your real telemetry URL.
  navigator.sendBeacon(
    '/analytics',
    JSON.stringify({
      name: metric.name,
      value: metric.value,
      navigationType: metric.navigationType,
      navigationURL: metric.navigationURL,
    }),
  );
}

// Pass { reportSoftNavs: true } to report metrics per soft navigation in addition
// to the initial hard navigation.
onLCP(sendToAnalytics, { reportSoftNavs: true });
onFCP(sendToAnalytics, { reportSoftNavs: true });
onCLS(sendToAnalytics, { reportSoftNavs: true });
onINP(sendToAnalytics, { reportSoftNavs: true });

सबसे सही तरीके

  • observer.observe() को कॉल करने से पहले, PerformanceObserver.supportedEntryTypes?.includes(...) का इस्तेमाल करके 'interaction-contentful-paint' और 'soft-navigation' की सुविधा का पता लगाएं.
  • 'interaction-contentful-paint' और 'soft-navigation' को मॉनिटर करते समय, { buffered: true } को ज़रूर पास करें. इससे, अगर Analytics कोड एसिंक्रोनस तरीके से शुरू होता है, तो शुरुआती एंट्री मिस नहीं होंगी.
  • ज़रूर: नई soft-navigation एंट्री को हैंडल करते समय softNavEntry.getLargestInteractionContentfulPaint() को कॉल करें, ताकि यूआरएल अपडेट होने से पहले हुए पेंट, एलसीपी में शामिल किए जा सकें.
  • ज़रूर: interaction-contentful-paint एंट्री को सॉफ़्ट नेविगेशन से मैच करें. इसके लिए, interactionId का इस्तेमाल करें, न कि navigationId का.
  • entry.presentationTime को सीधे तौर पर रिपोर्ट करने के बजाय, इंटरैक्शन पेंट लेटेंसी को मेज़र करने के लिए, entry.duration (या entry.presentationTime - entry.startTime) का इस्तेमाल करें. सभी PerformanceEntry टाइमस्टैंप, शुरुआती हार्ड पेज लोड अवधि के समय से मेज़र किए जाते हैं, न कि सॉफ़्ट नेविगेशन की शुरुआत से.
  • लंबे समय तक इस्तेमाल किए जाने वाले एक पेज के ऐप्लिकेशन को मॉनिटर करने के लिए, performance.getEntriesByType('soft-navigation') पर भरोसा न करें. ऐसा इसलिए, क्योंकि ब्राउज़र का बफ़र, सॉफ़्ट नेविगेशन की शुरुआती 50 एंट्री तक ही सीमित होता है.

फ़ॉलबैक रणनीतियां

अगर आपके बेसलाइन टारगेट में ऐसे ब्राउज़र शामिल हैं जो InteractionContentfulPaint या PerformanceSoftNavigation के साथ काम नहीं करते, तो इन एपीआई को रीयल यूज़र मॉनिटरिंग (आरयूएम) के लिए, परतदार वृद्धि के तौर पर इस्तेमाल करें:

  1. सुविधा की पहचान करना और अनुकूल गिरावट: PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint') और PerformanceObserver.supportedEntryTypes?.includes('soft-navigation') की मदद से, ऑब्ज़र्वर रजिस्ट्रेशन को सुरक्षित रखें. जिन ब्राउज़र में यह सुविधा काम नहीं करती उनमें ऑब्ज़र्वर को रजिस्टर न करें, ताकि आपका ऐप्लिकेशन बिना किसी गड़बड़ी के काम कर सके.
  2. स्टैंडर्ड हार्ड-नेविगेशन ऑब्ज़र्वर को चालू रखें: सभी ब्राउज़र पर स्टैंडर्ड 'largest-contentful-paint', 'event', और 'layout-shift' एंट्री को मॉनिटर करना जारी रखें. LargestContentfulPaint को उपयोगकर्ता के पहले इंटरैक्शन पर तय किया जाता है. भले ही, सॉफ़्ट-नेविगेशन की सुविधा काम करती हो या नहीं. इसलिए, सॉफ़्ट-नेविगेशन की सुविधा वाले ब्राउज़र में सॉफ़्ट नेविगेशन को मेज़र करने से, शुरुआती हार्ड-लोड एलसीपी मेज़रमेंट में कोई बदलाव नहीं होता या वे नहीं टूटते.