可靠地評估完整工作階段數據分析和遙測資料

如要可靠地追蹤涵蓋使用者造訪網頁整個過程 (不只是載入網頁前) 的數據分析、Core Web Vitals 和遙測資料,請使用 fetchLater() API。

如果不需要回應,且傳送時間不緊急,fetchLater() API 是最可靠的資料傳送方式,適用於使用者分析、遙測、錯誤追蹤和 Core Web Vitals 等成效指標。

在 unload 事件監聽器中建立 <img> 像素等舊技術,向來不可靠 (尤其是在行動裝置上),且可能會對效能造成負面影響 (導致網頁不符合 bfcache 資格)。

導入方式

  1. 排定要求:相關資料可用時,請立即使用資料酬載呼叫 fetchLater()。這會將資料排入佇列,稍後再傳送。

  2. 視需要更新酬載:如果使用者產生更多資料或狀態變更,請取消先前排定的要求,並使用完整更新的快照再次呼叫 fetchLater()。

  3. 讓瀏覽器處理其餘作業:使用者離開或關閉分頁時,瀏覽器會可靠地傳送最近一次 fetchLater() 呼叫的酬載。

範例程式碼

這段程式碼會使用 fetchLater(),每 10 秒將新的信標排入佇列,並更新工作階段持續時間,藉此評估使用者造訪網頁的工作階段持續時間。

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

最佳做法

  • 建議在不需要回應,且不急著傳送資料的情況下,使用 fetchLater() 將資料傳送至伺服器。
  • 建議使用 AbortController 取消待處理的擷取作業,以免使用者離開頁面前需要更新資料
  • 務必盡可能縮減酬載大小,以免超出配額 (目前每個來源約 64 KB)。
  • 務必將 fetchLater() 呼叫包裝在 try/catch 中,以便處理配額錯誤。
  • 務必偵測 globalThis 上是否有 fetchLater(),並為不支援 API 的瀏覽器實作備用策略。
  • 請勿使用 ReadableStream 物件做為要求主體,否則會發生錯誤。

瀏覽器支援和備用策略

如果fetchLater()未達到基準目標,則必須採用備用策略。不過,考量到這個 API 的可靠性和效能優勢,如果瀏覽器支援,建議使用 fetchLater()。

建議的備用策略是使用下方的 polyfill,該 polyfill 會在內部使用 fetch() 搭配 keepalive 或 navigator.sendBeacon() 處理不支援的瀏覽器。您的程式碼必須直接呼叫 fetchLater(),切勿自行呼叫 fetch()、sendBeacon() 或其他信標 API。

fetchLater() polyfill

請使用下列最小 fetchLater() polyfill,在不支援的瀏覽器中盡可能實作 API。

這個 polyfill 唯一顯著的行為差異,在於它使用 visibilitychange 偵測使用者何時離開,而不是依賴瀏覽器的原生卸載處理。這是內部實作詳細資料,您的程式碼不需要監聽 visibilitychange 或任何其他頁面生命週期事件。只要呼叫 fetchLater(),polyfill 就會處理傳送作業。

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