Debuguj wydajność w terenie

Dowiedz się, jak przypisywać dane o skuteczności do 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 laboratoryjne: takie jak Lighthouse, gdzie strona jest wczytywana w symulowanym środowisku, które może naśladować różne warunki (np. powolna sieć i tańsze urządzenie mobilne).
  • Narzędzia w terenie: np. Raport na temat użytkowania Chrome (CrUX), który opiera się na zbiorczych danych rzeczywistych użytkowników Chrome. Pamiętaj, że dane rejestrowane przez narzędzia takie jak PageSpeed Insights i Search Console pochodzą z danych na temat użytkowania Chrome.

O ile narzędzia terenowe zapewniają dokładniejsze dane, czyli takie, które faktycznie odzwierciedlają wrażenia rzeczywistych użytkowników, narzędzia laboratoryjne często są lepsze w identyfikowaniu i rozwiązywaniu problemów.

Dane raportu na temat użytkowania Chrome lepiej odzwierciedlają rzeczywistą wydajność strony, ale znajomość wyników tego typu raczej nie pomoże Ci ustalić, jak poprawić wydajność.

Lighthouse natomiast rozpozna problemy i zaproponuje sposoby ich poprawy. Narzędzie Lighthouse będzie jednak podpowiadać tylko problemy z wydajnością wykryte podczas wczytywania strony. Nie wykrywa problemów, które pojawiają się tylko w wyniku interakcji użytkownika, np. przewijania lub klikania przycisków na stronie.

Tu 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 prawdziwych użytkowników z tej dziedziny?

W tym poście szczegółowo opisujemy, jakich interfejsów API możesz używać do zbierania dodatkowych informacji na temat debugowania każdego z bieżących podstawowych wskaźników internetowych, a także podpowiadamy, 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 w polu informacji o debugowaniu. Wartość CLS jest mierzona przez cały okres obsługi strony, więc sposób interakcji użytkownika z nią (jak daleko przewija stronę, co klika itp.) może mieć znaczny wpływ na to, czy występują przesunięcia układu i które elementy się przesuwają.

Przyjrzyj się temu raportowi PageSpeed Insights:

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

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

Nawet jeśli zdajesz sobie sprawę, że interakcja użytkownika wpływa na dane pól, musisz wiedzieć, które elementy strony się przesuwają, dając wynik 0,3 przy 75.percentylu.

Jest to możliwe dzięki interfejsowi LayoutShiftAttribution.

Pobranie atrybucji przesunięcia układu

Interfejs LayoutShiftAttribution jest widoczny w każdym wpisie layout-shift generowanym przez interfejs Layout Instability 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 programista może obserwować każde przesunięcie układu, które ma miejsce na stronie, a także to, które elementy się w nich poruszają.

Oto przykładowy kod rejestrujący każde przesunięcie układu oraz 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 układu prawdopodobnie nie jest praktyczne. Jednak dzięki monitorowaniu wszystkich zmian możesz śledzić największe zmiany i po prostu raportować o nich informacje.

Celem nie jest identyfikowanie i naprawianie wszystkich pojedynczych zmian układu, które zachodzą u każdego użytkownika. Celem jest identyfikacja tych zmian, które mają wpływ na największą liczbę użytkowników, a tym samym największy wpływ na CLS strony na 75. percentylu.

Nie trzeba też obliczać największego elementu źródłowego przy każdej zmianie. Wystarczy to zrobić, gdy już przygotujesz wartość CLS do wysłania 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ększej zmiany:

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

Po zidentyfikowaniu składnika, który 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 w przypadku danej strony, prawdopodobnie będzie się różnić w zależności od użytkownika, ale jeśli zgromadzisz te elementy w przypadku wszystkich użytkowników, możliwe będzie wygenerowanie listy przesuwających się elementów mających wpływ na największą liczbę użytkowników.

Po zidentyfikowaniu i usunięciu głównej przyczyny tych zjawisk kod Analytics zacznie rejestrować mniejsze zmiany jako „najgorsze” zmiany na stronach. Ostatecznie wszystkie raportowane zmiany będą na tyle niewielkie, że Twoje strony będą mieściły się w dobrym progu, który wynosi 0,1.

Inne metadane, które mogą być przydatne do rejestrowania razem z elementem źródłowym największego przesunięcia, to:

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

LCP

Aby debugować LCP w polu, najważniejsze są informacje o tym, który element był największym elementem (element kandydujący LCP) podczas danego wczytywania strony.

Pamiętaj, że możliwe, że element składowy LCP będzie zupełnie inny, nawet w przypadku tej samej strony, jest całkowicie możliwe – jest to dość powszechne.

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, które zostały przewinięte na samą górę. Linki często zawierają identyfikatory fragmentów, a nawet fragmenty tekstu, co oznacza, że strony mogą wczytywać się i wyświetlać w dowolnym miejscu po przewinięciu.
  • Treści mogą być personalizowane pod kątem bieżącego użytkownika, więc element kandydujący LCP może być bardzo zróżnicowany.

Oznacza to, że nie można wyciągać założeń na temat tego, który element lub zbiór elementów będzie najczęściej wybieranym elementem LCP w przypadku danej strony. Musisz mierzyć ją na podstawie rzeczywistych zachowań użytkowników.

Wskaż element kandydujący LCP

Aby określić element kandydujący LCP w języku JavaScript, 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 LCP, sprawdzając właściwość element ostatniej pozycji:

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

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

Oprócz elementu kandydującego LCP przydatny może być też pomiar podczęści LCP, co przydaje się przy określaniu, które konkretne kroki optymalizacji są istotne w przypadku Twojej witryny.

FID

Aby debugować FID w tym polu, pamiętaj, że mierzy tylko część opóźnienia ogólnego pierwszego opóźnienia zdarzenia wejściowego. Oznacza to, że to, z czym użytkownik wszedł w interakcję, nie jest tak naprawdę ważne, jak to, co jeszcze działo 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 wejdzie w interakcję z danymi wprowadzanymi przez użytkownika – czyli jeszcze przed zakończeniem wczytywania kodu JavaScriptu wymaganego do zapewnienia interaktywności treści.

W przypadku tego typu zastosowań bardzo ważne może być sprawdzenie, czy pierwsze dane wejściowe wystąpiły przed nawodnieniem, czy po nim. Jeśli okaże się, że wiele osób próbuje wejść w interakcję ze stroną, zanim się zakończy, rozważ renderowanie stron w stanie wyłączonym lub wczytania, a nie w stanie, które wygląda na interaktywną.

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

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

Ten kod obserwuje wpisy first-input i rejestruje, czy pierwsze dane wejściowe miały miejsce 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 FID i typ zdarzenia

Dodatkowe potencjalnie użyteczne sygnały debugowania to element, z którym nastąpiła interakcja, a także typ interakcji (np. mousedown, keydown, pointerdown). Choć sama interakcja z elementem nie wpływa na FID (wartość FID to tylko opóźnienie w całkowitym opóźnieniu zdarzenia), informacje o tym, z którymi elementami użytkownicy wchodzą w interakcję, mogą pomóc w ustaleniu, jak najlepiej poprawić FID.

Jeśli np. zdecydowana większość pierwszych interakcji użytkownika dotyczy określonego elementu, warto rozważyć wbudowanie niezbędnego dla tego elementu kodu JavaScript w kodzie HTML, a pozostałe leniwe wczytywanie.

Aby uzyskać typ interakcji i element powiązany 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

INP jest bardzo podobny do FID pod tym względem:

  1. z jakim elementem nastąpiła interakcja;
  2. dlaczego był to typ interakcji,
  3. Kiedy ta interakcja miała miejsce

Tak jak w przypadku FID, główną przyczyną powolnych interakcji jest zablokowanie wątku głównego, co może się często zdarzyć podczas wczytywania JavaScriptu. Wiedza o tym, czy większość powolnych interakcji zachodzi podczas wczytywania strony, pomaga ustalić, co należy zrobić, aby rozwiązać problem.

W przeciwieństwie do FID dane INP uwzględniają pełny czas oczekiwania na interakcję, 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 są rodzaje interakcji.

INP i FID opierają się na Event Timing API, więc sposób określania tych informacji w JavaScripcie 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ż to wymaga bardziej skomplikowanej logiki. Z kolei poniżej dowiesz się, jak uzyskać te informacje, korzystając z biblioteki JavaScript web-vitals.

Wykorzystanie z biblioteką JavaScript Web-vitals

W sekcjach powyżej znajdziesz kilka ogólnych sugestii i przykładów kodu, które pomogą Ci zebrać informacje debugowania, które następnie należy uwzględnić w danych wysyłanych do narzędzia analitycznego.

Od wersji 3 biblioteka JavaScript web-vitals zawiera kompilację atrybucji, która uwzględnia wszystkie te informacje, a także kilka dodatkowych sygnałów.

Z przykładu poniżej dowiesz się, 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 jest specyficzny dla Google Analytics, ale ogólna koncepcja powinna się też wiązać z innymi narzędziami analitycznymi.

Ten kod pokazuje też, jak raportować dane o pojedynczym sygnale debugowania, ale może też przydać się do 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 działania oraz elemencie, z którym wchodzisz 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 w życiach internetowych.

Tworzenie raportów i wizualizowanie danych

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

Jak już wspomnieliśmy, nie musisz rozwiązywać każdego problemu, z którym spotykają się użytkownicy, tylko najpierw musisz zająć się problemami, które mają wpływ na największą liczbę użytkowników. Powinno to być też te, które mają największy negatywny wpływ na Twoje wyniki w podstawowych wskaźnikach internetowych.

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

Podsumowanie

Mamy nadzieję, że te informacje omówiły konkretne sposoby wykorzystania istniejących interfejsów API do zarządzania wydajnością i biblioteki web-vitals do uzyskiwania informacji debugowania, które pomogą w diagnozowaniu skuteczności na podstawie rzeczywistych wizyt użytkowników w tej dziedzinie. Chociaż w tym przewodniku skupiamy się na podstawowych wskaźnikach internetowych, pojęcia te mają też zastosowanie do debugowania wszystkich danych o wydajności, które można zmierzyć w kodzie JavaScript.

Jeśli dopiero zaczynasz mierzyć skuteczność i korzystasz już z Google Analytics, dobrym punktem wyjścia może być narzędzie do raportowania wskaźników internetowych, które obsługuje już raportowanie danych debugowania z podstawowych wskaźników internetowych.

Jeśli jesteś dostawcą rozwiązań analitycznych i chcesz ulepszać swoje produkty oraz zapewniać użytkownikom więcej informacji na temat debugowania, weź pod uwagę niektóre z opisanych tu metod. Nie ograniczaj się jednak do tylko przedstawionych tu pomysłów. Ten post jest skierowany do wszystkich narzędzi analitycznych, ale poszczególne narzędzia analityczne mogą (i powinny) rejestrować i raportować jeszcze więcej danych na potrzeby 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.