Debuguj wydajność w terenie

Dowiedz się, jak przypisywać dane o skuteczności za pomocą danych debugowania, aby wykrywać i rozwiązywać problemy użytkowników w Analytics

Google udostępnia 2 kategorie narzędzi do pomiaru i debugowania wydajności:

  • Narzędzia Laboratorium: narzędzia takie jak Lighthouse, w których strona jest wczytywana w symulowanym środowisku, które może naśladować różne warunki (np. wolną sieć i tańsze urządzenie mobilne).
  • Narzędzia w terenie: takie jak Raport na temat użytkowania Chrome (CrUX), który jest oparty na zbiorczych danych rzeczywistych użytkowników Chrome. (Pamiętaj, że dane pól raportowane przez narzędzia takie jak PageSpeed Insights i Search Console pochodzą z danych na temat użytkowania Chrome).

Chociaż narzędzia terenowe zapewniają dokładniejsze dane, które faktycznie odzwierciedlają wrażenia rzeczywistych użytkowników, narzędzia te często są skuteczniejsze w identyfikowaniu i rozwiązywaniu problemów.

Dane raportu CrUX lepiej odzwierciedlają rzeczywistą wydajność strony, ale znajomość wyników tego raportu raczej nie pomoże Ci w określeniu, jak poprawić wydajność.

Narzędzie Lighthouse natomiast wykryje problemy i zaproponuje sposoby ich poprawy. Pamiętaj jednak, że jeśli wykryje ono problemy z wydajnością podczas wczytywania strony, pokaże tylko sugestie. Nie wykrywa problemów, które pojawiają się tylko w wyniku interakcji użytkownika, np. przewijania czy klikania przycisków na stronie.

Pojawia się ważne pytanie: jak można uzyskać informacje o debugowaniu podstawowych wskaźników internetowych lub innych danych dotyczących wydajności od rzeczywistych użytkowników w terenie?

W tym poście szczegółowo wyjaśnimy, jakich interfejsów API możesz używać do zbierania dodatkowych informacji na potrzeby debugowania każdego z bieżących podstawowych wskaźników internetowych. Podamy też pomysły, jak rejestrować te dane w istniejącym narzędziu analitycznym.

Interfejsy API do atrybucji i debugowania

CLS

Spośród wszystkich podstawowych wskaźników internetowych to właśnie CLS ma największe znaczenie dla zbierania danych debugowania w polu. CLS jest mierzony przez cały okres korzystania ze strony, a to sposób, w jaki użytkownik wchodzi z nią w interakcję – jak daleko przewija stronę, co klika itd. – może mieć znaczny wpływ na to, czy występują przesunięcia układu i które elementy się przesuwają.

Przeanalizuj ten raport z PageSpeed Insights:

Raport PageSpeed Insights z różnymi wartościami CLS

Wartość CLS w laboratorium (Lighthouse) i wskaźnik CLS z tego pola (dane z CrUX) różnią się od siebie. Ma to sens, jeśli weźmiesz pod uwagę, że strona może zawierać dużo interaktywnych treści, które nie są używane podczas testowania w Lighthouse.

Nawet jeśli zdajesz sobie sprawę, że interakcja użytkownika wpływa na dane pola, musisz wiedzieć, które elementy strony się przenoszą, aby dać wynik 0,3 dla 75.percentyla.

Jest to możliwe dzięki interfejsowi LayoutShiftAttribution.

Pobieranie atrybucji przesunięcia układu

Interfejs LayoutShiftAttribution jest widoczny w każdym wpisie layout-shift wyświetlanym w elemencie interfejsu Układ API.

Szczegółowe omówienie obu tych interfejsów znajdziesz w sekcji Debugowanie zmian układu. Na potrzeby tego postu przede wszystkim musisz wiedzieć, że jako deweloper możesz obserwować każde przesunięcie układu zachodzące na stronie, a także to, które elementy się zmieniają.

Oto przykładowy kod, który rejestruje każde przesunięcie układu, a także przesunięte elementy:

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});

Pomiar i wysyłanie danych do narzędzia analitycznego w przypadku każdego pojedynczego przesunięcia prawdopodobnie nie jest praktyczne

Nie chodzi o wykrycie i naprawienie wszystkich pojedynczych zmian układu, które zachodzą u każdego użytkownika. Celem jest wykrycie tych zmian, które mają wpływ na największą liczbę użytkowników, a tym samym w największym stopniu przyczyniają się do CLS strony w 75. percentylu.

Nie musisz też obliczać największego elementu źródłowego przy każdej zmianie. Wystarczy, że zrobisz to tylko wtedy, gdy przygotujesz się do wysłania wartości CLS do narzędzia analitycznego.

Ten kod pobiera listę wpisów layout-shift, które mają udział w CLS, i zwraca największy element źródłowy z największego przesunięcia:

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;
    }
  }
}

Gdy dowiesz się, który element ma największy wpływ na największą zmianę, możesz to zgłosić w narzędziu analitycznym.

Element, który w największym stopniu wpływa na CLS danej strony, prawdopodobnie będzie się różnił w przypadku poszczególnych użytkowników, ale jeśli zgrupujesz te elementy dotyczące wszystkich użytkowników, będziesz mieć możliwość wygenerowania listy zmieniających się elementów, które mają wpływ na największą liczbę użytkowników.

Gdy już określisz i usuniesz główną przyczynę tych zmian, kod Analytics zacznie raportować mniejsze zmiany jako „najgorsze” przesunięcia na stronach. Ostatecznie wszystkie zgłaszane zmiany będą na tyle niewielkie, że Twoje strony mieszczą się w progu „dobrej” wartości, czyli 0,1.

Inne metadane, które mogą być przydatne do przechwytywania, razem z elementem źródłowym z największym przesunięciem:

  • Czas największej zmiany
  • Ścieżka adresu URL w momencie największych zmian (w przypadku witryn, które dynamicznie aktualizują adres URL, np. aplikacji na jednej stronie).

LCP

Aby debugować LCP w tym polu, potrzebna jest przede wszystkim to, który element był największym elementem (element kandydujący LCP) w przypadku danego wczytania strony.

Pamiętaj, że możliwe jest, że element kandydujący LCP będzie inny, nawet w przypadku tej samej strony, niezależnie od użytkownika.

Taka sytuacja może mieć miejsce z kilku powodów:

  • Urządzenia użytkowników mają różną rozdzielczość ekranu, co powoduje odmienne układy stron i wyświetlanie różnych elementów w widocznym obszarze.
  • Użytkownicy nie zawsze wczytują strony przewinięte na samą górę. Linki często zawierają identyfikatory fragmentów, a nawet fragmenty tekstu, co oznacza, że możliwe jest ładowanie stron i wyświetlanie ich w dowolnym miejscu, w którym można przewijać stronę.
  • Treści mogą być personalizowane pod kątem bieżącego użytkownika, więc element kandydujący do LCP może się znacznie różnić w zależności od użytkownika.

Oznacza to, że nie możesz zakładać, który element lub zbiór elementów będzie najczęstszym elementem kandydującym LCP w przypadku danej strony. Musi być ona oceniana na podstawie rzeczywistych zachowań użytkowników.

.

Określ element kandydujący LCP

Aby określić element kandydujący LCP w JavaScripcie, możesz użyć największego interfejsu Contentful Paint API, czyli tego samego interfejsu API, którego używasz do określania wartości czasu LCP.

Obserwując wpisy largest-contentful-paint, możesz określić bieżący element kandydujący do LCP, sprawdzając właściwość element ostatniego wpisu:

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});

Znając element kandydujący LCP, możesz wysłać go do narzędzia analitycznego razem z wartością wskaźnika. Podobnie jak w przypadku CLS pomaga to określić, które elementy należy zoptymalizować w pierwszej kolejności.

Oprócz elementu kandydującego LCP może się też przydać pomiar czasów trwania podczęści LCP, co przydaje się przy określaniu, które etapy optymalizacji warto zastosować w przypadku Twojej witryny.

FID

Aby debugować FID w tym polu, pamiętaj, że FID mierzy tylko opóźnienie ogólnego pierwszego opóźnienia zdarzenia wejściowego. Oznacza to, że to, z czym użytkownik wchodził w interakcję, nie jest tak naprawdę ważne jak to, co dzieje się w wątku głównym w momencie interakcji.

Na przykład wiele aplikacji JavaScript obsługujących renderowanie po stronie serwera (SSR) dostarcza statyczny kod HTML, który można wyrenderować na ekranie, zanim nastąpi jego interakcja z danymi wprowadzanymi przez użytkownika, czyli zanim JavaScript wymagany do zapewnienia interaktywności treści zakończy się wczytywaniem treści.

W przypadku takich zastosowań bardzo ważne może być sprawdzenie, czy pierwsze dane wejściowe miały miejsce przed nawodnieniem, czy po nim. Jeśli okaże się, że wiele osób próbuje wejść w interakcję ze stroną, zanim działanie wody się zakończy, rozważ renderowanie stron w stanie nieaktywnym lub wczytywania, a nie w stanie, który wygląda na interaktywny.

Jeśli platforma aplikacji ujawnia sygnaturę czasową nawodnienia, możesz porównać ją z sygnaturą czasową wpisu first-input, aby określić, czy pierwsze dane wejściowe miały miejsce przed nawodnieniem czy po nim. Jeśli platforma nie udostępnia tej sygnatury czasowej lub w ogóle nie stosuje nawodnienia, innym przydatnym sygnałem może być to, czy dane wejściowe wystąpiły przed zakończeniem wczytywania JavaScriptu lub po nim.

Zdarzenie DOMContentLoaded jest uruchamiane po całkowitym wczytaniu i analizie kodu HTML strony, co obejmuje oczekiwanie na wczytanie wszystkich skryptów synchronicznych, odroczonych lub modułów (w tym wszystkich modułów zaimportowanych statycznie). Możesz więc wykorzystać moment wystąpienia zdarzenia i porównać go z czasem wystąpienia FID.

Ten kod obserwuje wpisy first-input i loguje, czy pierwsze dane wejściowe nastąpiły przed końcem zdarzenia DOMContentLoaded:

new PerformanceObserver((list) => {
  const fidEntry = list.getEntries()[0];
  const navEntry = performance.getEntriesByType('navigation')[0];
  const wasFIDBeforeDCL =
    fidEntry.startTime < navEntry.domContentLoadedEventStart;

  console.log('FID occurred before DOMContentLoaded:', wasFIDBeforeDCL);
}).observe({type: 'first-input', buffered: true});

Określ element docelowy i typ zdarzenia FID

Dodatkowe potencjalnie przydatne sygnały debugowania to element, z którym nastąpiła interakcja, a także typ interakcji (np. mousedown, keydown, pointerdown). Choć interakcja z samym elementem nie wpływa na FID (pamiętać FID to tylko opóźniona część całkowitego czasu oczekiwania na zdarzenie), informacja o tym, z którymi elementami użytkownicy wchodzą w interakcję, może pomóc w określeniu, jak najlepiej ulepszyć FID.

Jeśli np. zdecydowana większość pierwszych interakcji użytkownika dotyczy konkretnego elementu, warto wbudować w kod HTML wymagany dla tego elementu kod JavaScript, a resztę wczytywać leniwie.

Aby uzyskać informacje o typie interakcji i elemencie powiązanym z pierwszym zdarzeniem wejścia, możesz odwołać się do właściwości target i name we wpisie first-input:

new PerformanceObserver((list) => {
  const fidEntry = list.getEntries()[0];

  console.log('FID target element:', fidEntry.target);
  console.log('FID interaction type:', fidEntry.name);
}).observe({type: 'first-input', buffered: true});

INP

Wartość INP jest bardzo podobna do FID pod tym względem:

  1. z jakim elementem nastąpiła interakcja;
  2. Dlaczego to była interakcja
  3. kiedy miała miejsce interakcja.

Tak jak w przypadku FID, główną przyczyną powolnych interakcji jest zablokowanie wątku głównego, co może często występować podczas wczytywania JavaScriptu. Wiedza o tym, czy podczas wczytywania strony zachodzi najwięcej wolnych interakcji, pomaga ustalić, co należy zrobić, by rozwiązać problem.

W przeciwieństwie do FID dane INP uwzględniają pełne opóźnienie interakcji – w tym czas potrzebny na uruchomienie zarejestrowanych detektorów zdarzeń, a także czas potrzebny na wyrenderowanie następnej klatki po wykonaniu wszystkich detektorów zdarzeń. Oznacza to, że w przypadku INP jeszcze lepiej jest wiedzieć, które elementy docelowe powodują powolne interakcje i jakie to rodzaje interakcji.

Wartości INP i FID opierają się na Event Timing API, więc sposób określania tych informacji w JavaScript jest bardzo podobny do poprzedniego przykładu. Ten kod loguje element docelowy i czas (w stosunku do DOMContentLoaded) wpisu INP.

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

  const navEntry = performance.getEntriesByType('navigation')[0];
  const wasINPBeforeDCL =
    inpEntry.startTime < navEntry.domContentLoadedEventStart;

  console.log('INP occurred before DCL:', wasINPBeforeDCL);
}

Pamiętaj, że ten kod nie pokazuje, jak określić, który wpis event jest wpisem INP, ponieważ ta logika jest bardziej skomplikowana. W tej sekcji wyjaśniamy jednak, jak uzyskać te informacje za pomocą biblioteki JavaScript web-vitals.

Wykorzystanie z biblioteką JavaScript Web-vitals

W sekcjach powyżej znajdziesz ogólne sugestie i przykłady kodu, które pomogą Ci rejestrować informacje o debugowaniu i uwzględniać je w danych przesyłanych do narzędzia analitycznego.

Od wersji 3 biblioteka JavaScript web-vitals zawiera komponent z atrybucją, który uwzględnia wszystkie te informacje, a także kilka dodatkowych sygnałów.

Poniższy przykładowy kod pokazuje, jak ustawić dodatkowy parametr zdarzenia (lub wymiar niestandardowy) zawierający ciąg debugowania, który pomaga w znalezieniu głównej przyczyny problemów z wydajnością.

import {onCLS, onFID, 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 'FID':
    case 'INP':
      eventParams.debug_target = attribution.eventTarget;
      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);

Ten kod dotyczy tylko Google Analytics, ale ogólne zasady powinny się też znaleźć w innych narzędziach analitycznych.

Ten kod tylko pokazuje, jak raportować dane o pojedynczym sygnale debugowania, ale może się też przydać możliwość zbierania i raportowania wielu różnych sygnałów dla poszczególnych rodzajów danych. Na przykład do debugowania INP możesz zbierać dane o typie interakcji, czasie trwania interakcji oraz o tym, z którym elementem wchodzą w interakcję. Kompilacja atrybucji web-vitals ujawnia wszystkie te informacje, jak w tym przykładzie:

import {onCLS, onFID, 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.eventTarget;
      eventParams.debug_type = attribution.eventType;
      eventParams.debug_time = attribution.eventTime;
      eventParams.debug_load_state = attribution.loadState;
      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);

Pełną listę ujawnionych sygnałów debugowania znajdziesz w dokumentacji atrybucji Web-witals.

Raportowanie i wizualizacja danych

Gdy zaczniesz zbierać informacje o debugowaniu wraz z wartościami danych, kolejnym krokiem jest zebranie danych o wszystkich użytkownikach, aby rozpocząć poszukiwanie wzorców i trendów.

Jak wspomnieliśmy powyżej, nie musisz rozwiązywać każdego problemu, który napotykają użytkownicy. Warto zająć się – zwłaszcza na początku – problemami, które mają wpływ na największą liczbę użytkowników. Powinny one również mieć największy negatywny wpływ na wyniki podstawowych wskaźników internetowych.

W przypadku GA4 przeczytaj osobny artykuł o tworzeniu zapytań i wizualizacji danych za pomocą BigQuery.

Podsumowanie

Mam nadzieję, że ten post omówił konkretne sposoby wykorzystania istniejących interfejsów API wydajności i biblioteki web-vitals do uzyskiwania informacji debugowania ułatwiających diagnozowanie skuteczności na podstawie rzeczywistych wizyt użytkowników w tej dziedzinie. Chociaż ten przewodnik skupia się na podstawowych wskaźnikach internetowych, pojęcia te mają też zastosowanie do debugowania dowolnych wskaźników wydajności mierzonych w JavaScript.

Jeśli dopiero zaczynasz mierzyć skuteczność i korzystasz już z Google Analytics, możesz użyć narzędzia do raportowania wskaźników internetowych, ponieważ obsługuje ono już raportowanie danych debugowania z podstawowych wskaźników internetowych.

Jeśli jesteś dostawcą usług analitycznych i chcesz ulepszać swoje produkty oraz zapewniać użytkownikom więcej informacji na potrzeby debugowania, weź pod uwagę niektóre z opisanych tu metod (nie ograniczaj się do tylko tych przedstawionych koncepcji). Ten post jest przeznaczony dla wszystkich narzędzi analitycznych, ale poszczególne narzędzia analityczne mogą (i powinny) rejestrować i raportować jeszcze więcej informacji na temat debugowania.

Jeśli uważasz, że z powodu brakujących funkcji lub informacji w samych interfejsach API występują luki w możliwości debugowania tych wskaźników, prześlij swoją opinię na adres web-vitals-feedback@googlegroups.com.