Niezawodne pomiary danych analitycznych i telemetrycznych dotyczących całej sesji

Aby rzetelnie śledzić statystyki, Core Web Vitals i dane telemetryczne, które obejmują całą wizytę użytkownika na stronie internetowej (nie tylko do momentu wczytania strony), użyj interfejsu API fetchLater().

Interfejs API fetchLater() to najbardziej niezawodny sposób wysyłania danych do serwera w przypadkach, gdy odpowiedź nie jest wymagana, a czas dostarczenia nie jest pilny. Dotyczy to danych takich jak statystyki użytkowników, dane telemetryczne, śledzenie błędów i wskaźniki skuteczności, np. Core Web Vitals.

Starsze techniki, takie jak tworzenie piksela <img> w detektorze zdarzeń unload, są bardzo zawodne (zwłaszcza na urządzeniach mobilnych) i mogą negatywnie wpływać na wydajność (uniemożliwiając korzystanie z pamięci podręcznej bfcache).

Jak to zrobić?

  1. Zaplanuj żądanie: gdy tylko będą dostępne odpowiednie dane, wywołaj fetchLater() z ładunkiem danych. Spowoduje to umieszczenie danych w kolejce do późniejszego wysłania.

  2. W razie potrzeby zaktualizuj ładunek: jeśli użytkownik wygeneruje więcej danych lub zmieni się stan, przerwij wcześniej zaplanowane żądanie i ponownie wywołaj fetchLater() z w pełni zaktualizowanym zrzutem.

  3. Pozwól przeglądarce zająć się resztą: gdy użytkownik opuści stronę lub zamknie kartę, przeglądarka niezawodnie wyśle ładunek z ostatniego wywołania fetchLater().

Przykładowy kod

Ten kod mierzy czas trwania sesji wizyty użytkownika na stronie, używając fetchLater() do kolejkowania nowego sygnału co 10 sekund ze zaktualizowanym czasem trwania sesji.

const ANALYTICS_ENDPOINT = '/path/to/analytics/endpoint';

const sessionData = {
  duration: 0,
  id: crypto.randomUUID(),
};

let fetchLaterController = null;

function queueBeacon() {
  // Abort any pending beacons before creating a new one.
  if (fetchLaterController) {
    fetchLaterController.abort();
  }
  fetchLaterController = new AbortController();

  // Update session duration to the current page time.
  sessionData.duration = performance.now();

  // Schedule a fetch for the data payload to be sent later.
  // IMPORTANT: wrap the call in a try/catch to handle quota errors.
  try {
    fetchLater(ANALYTICS_ENDPOINT, {
      method: 'POST',
      headers: {'content-type': 'application/json'},
      body: JSON.stringify(sessionData),
      signal: fetchLaterController.signal,
    });
  } catch (error) {
    // Handle errors as needed.
  }
}

// Update the session data and queue a new beacon every 10 seconds.
setInterval(queueBeacon, 10000);

Sprawdzone metody

  • UŻYWAJ metody fetchLater() do wysyłania danych na serwer w każdej sytuacji, gdy odpowiedź nie jest konieczna i nie jest istotne, aby dane zostały wysłane natychmiast.
  • UŻYWAJ AbortController, aby anulować oczekujące pobieranie w przypadkach, gdy dane mogą wymagać aktualizacji, zanim użytkownik opuści stronę.
  • DO minimum the payload size to avoid exceeding the quota (currently roughly 64KB per origin).
  • ZALECENIE: wywołania fetchLater() umieszczaj w try/catch, aby obsługiwać błędy związane z limitami.
  • DO feature detect the presence of fetchLater() on globalThis and implement a fallback strategy for browsers that don't support the API.
  • NIE używaj obiektu ReadableStream w treści żądania, ponieważ spowoduje to błąd.

Obsługa przeglądarek i strategie zastępcze

Jeśli fetchLater() nie osiągnie celu podstawowego, wymagana jest strategia zastępcza. Jednak ze względu na większą niezawodność i wydajność tego interfejsu API należy używać fetchLater(), jeśli przeglądarka go obsługuje.

Zalecana strategia zastępcza polega na użyciu poniższego kodu polyfill, który wewnętrznie obsługuje nieobsługiwane przeglądarki za pomocą fetch() z keepalive lub navigator.sendBeacon(). Twój kod MUSI bezpośrednio wywoływać funkcję fetchLater() – nigdy nie wywołuj samodzielnie funkcji fetch(), sendBeacon() ani innych interfejsów API do sygnałów.

fetchLater() polyfill

Użyj tego minimalnego kodu polyfill fetchLater(), który implementuje interfejs API w jak największym stopniu w nieobsługiwanych przeglądarkach.

Jedyną istotną różnicą w działaniu tego kodu polyfill jest to, że do wykrywania, kiedy użytkownik opuszcza stronę, używa on visibilitychange, a nie natywnej obsługi wyładowania w przeglądarce. Jest to wewnętrzny szczegół implementacji – Twój kod nie musi nasłuchiwać zdarzenia visibilitychange ani żadnych innych zdarzeń cyklu życia strony. Wystarczy wywołać fetchLater(), a polyfill zajmie się dostarczeniem.

globalThis.fetchLater ??= function fetchLater(url, init = {}) {
  let timeoutHandle;
  let activated = false;

  function sendNow() {
    if (!(init.signal && init.signal.aborted)) {
      // Use fetch keepalive if the browser supports it or if custom fetch
      // parameters are specified (e.g. custom headers or methods).
      // Otherwise fall back to `navigator.sendBeacon()`.
      if (
        'keepalive' in Request.prototype ||
        init.method !== 'POST' ||
        init.headers
      ) {
        fetch(url, Object.assign({}, init, {keepalive: true}));
        activated = true;
      } else {
        activated = navigator.sendBeacon(url, init.body);
      }
    }
    destroy();
  }

  function destroy() {
    document.removeEventListener('visibilitychange', sendNow);
    clearTimeout(timeoutHandle);
  }

  if (document.visibilityState === 'hidden') {
    // If the beacon was created while the page is already hidden, send data
    // ASAP but wait until the next microtask to allow all sync code to run.
    queueMicrotask(sendNow);
  } else {
    document.addEventListener('visibilitychange', sendNow);

    if (typeof init.activateAfter === 'number' && init.activateAfter >= 0) {
      timeoutHandle = setTimeout(sendNow, init.activateAfter);
    }
  }

  if (init.signal) {
    init.signal.addEventListener('abort', destroy);
  }

  return {
    get activated() {
      return activated;
    },
  };
};