Using the fetchLater() API
Shidhin

Shidhin @shidhincr

About: Hi, I am Shidhin. I am a Google Developer Expert in Web technologies. I work as a Staff front-end engineer in Atlan

Location:
India
Joined:
Nov 15, 2018

Using the fetchLater() API

Publish Date: Jun 26
0 0

Using the fetchLater() API: Reliable Beaconing for the Modern Web

Chrome 135, released in April 2025, introduces a powerful new addition to web developers' toolkit: the fetchLater() API. This feature solves a long-standing challenge of reliably sending data to servers when a user leaves a page. In this post, we'll explore what the API offers, why it matters, and how to start using it in your applications today.

The Problem with Page Unload Beaconing

Web developers often need to send critical data back to servers when users leave a page. Analytics services, performance metrics, and user interaction tracking all rely on this capability. Traditionally, developers have used a combination of approaches:

  • Listening for beforeunload, pagehide, or visibilitychange events
  • Using navigator.sendBeacon() or fetch() with the keepalive option

However, these methods face reliability issues, especially on mobile devices where events may not fire consistently. Even with the best practices, developers could only achieve around 82% reliability for unload beaconing.

Enter fetchLater()

The fetchLater() API provides a cleaner, more reliable solution. It allows you to queue up requests that will be sent either:

  1. When the page is unloaded (tab closed, navigation away, etc.)
  2. After a specified timeout
  3. When the browser decides it's time to send it

Basic Usage

The API syntax is designed to feel familiar to developers who use fetch():

// Basic usage - will send when page unloads
const result = fetchLater('/analytics-endpoint', {
  method: 'POST',
  body: JSON.stringify({ sessionData: collectSessionData() })
});

// Check if the request has been sent
console.log(result.activated); // false initially, true after sending
Enter fullscreen mode Exit fullscreen mode

Adding a Timeout

You can specify an activateAfter option (in milliseconds) to ensure the request is sent after a certain time, even if the page hasn't been unloaded:

// Send after 1 minute OR when page unloads (whichever comes first)
const oneMinute = 60 * 1000;
fetchLater('/analytics-endpoint', {
  method: 'POST',
  body: JSON.stringify({ sessionData: collectSessionData() }),
  activateAfter: oneMinute
});
Enter fullscreen mode Exit fullscreen mode

Updating Data Before Sending

One of the most powerful features is the ability to update the queued request before it's sent. This is perfect for analytics that accumulate data over time:

const analyticsData = {
  pageViews: 1,
  interactions: []
};

let controller = new AbortController();
let fetchLaterResult;

function queueAnalyticsBeacon() {
  // Abort any existing request
  controller.abort();
  controller = new AbortController();

  // Queue a new request with the latest data
  fetchLaterResult = fetchLater('/analytics-endpoint', {
    method: 'POST',
    body: JSON.stringify(analyticsData),
    signal: controller.signal,
    activateAfter: 60 * 60 * 1000 // 1 hour
  });
}

// Initial queue
queueAnalyticsBeacon();

// Update when user interacts
function recordInteraction(interactionType) {
  analyticsData.interactions.push({
    type: interactionType,
    timestamp: Date.now()
  });

  // Update the queued beacon
  queueAnalyticsBeacon();
}
Enter fullscreen mode Exit fullscreen mode

Real-World Example: Web Vitals Reporting

Here's how you might use fetchLater() to send Core Web Vitals data:

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

const metrics = new Set();
let controller;
let fetchLaterResult;

function updateMetrics(metric) {
  // Clear already-sent metrics if request was activated
  if (fetchLaterResult?.activated) {
    metrics.clear();
  }

  // Add the new/updated metric
  metrics.add(metric);

  // Prepare the data payload
  const body = JSON.stringify(Array.from(metrics));

  // Abort any existing fetchLater() and queue a new one
  controller?.abort();
  controller = new AbortController();
  fetchLaterResult = fetchLater('/vitals-analytics', {
    method: 'POST',
    body,
    signal: controller.signal,
    activateAfter: 60 * 60 * 1000 // 1 hour timeout for regular sending
  });
}

// Register the web vitals callbacks
onCLS(updateMetrics);
onINP(updateMetrics);
onLCP(updateMetrics);
Enter fullscreen mode Exit fullscreen mode

Error Handling

It's good practice to handle potential errors, especially quota limitations:

try {
  fetchLater('/analytics-endpoint', {
    method: 'POST',
    body: JSON.stringify(analyticsData)
  });
} catch (e) {
  if (e instanceof QuotaExceededError) {
    // Handle quota exceeded
    console.warn('Analytics beacon quota exceeded');
  } else {
    // Handle other errors
    console.error('Error queueing analytics beacon:', e);
  }
}
Enter fullscreen mode Exit fullscreen mode

Understanding Quotas and Limitations

To prevent abuse, browsers enforce limits on fetchLater() usage:

  • Total bandwidth for a top-level document is capped at 640KB
  • Each reporting origin is limited to 64KB
  • All pending requests are flushed when a document enters the browser's back/forward cache

Browser Support

As of May 2025:

  • Chrome: Supported since version 135 (April 2025)
  • Firefox: Under consideration
  • Safari: Not yet implemented

For browsers that don't support fetchLater(), you can implement a fallback using the traditional approach:

function sendAnalytics(data) {
  if (typeof window.fetchLater === 'function') {
    try {
      fetchLater('/analytics', {
        method: 'POST',
        body: JSON.stringify(data)
      });
      return true;
    } catch (e) {
      console.warn('fetchLater failed, falling back to sendBeacon', e);
    }
  }

  // Fallback to sendBeacon
  return navigator.sendBeacon('/analytics', JSON.stringify(data));
}

// Register traditional unload handlers as fallback
window.addEventListener('pagehide', () => {
  sendAnalytics(analyticsData);
});

window.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    sendAnalytics(analyticsData);
  }
});
Enter fullscreen mode Exit fullscreen mode

Demo

Image description

I have built a simple Demo page for the fetchLater API here: https://www.undefinednull.com/fetch-later-demo/

Conclusion

The fetchLater() API represents a significant improvement for web developers who need reliable beaconing. It simplifies code, improves reliability, and provides a more declarative approach to handling page unload data transmission.

By adopting this API (with appropriate fallbacks), developers can ensure their analytics, metrics, and other critical data have the best chance of making it back to servers, regardless of how users exit their web applications.

For more details, check the official documentation and consider experimenting with the API in your applications today.

Comments 0 total

    Add comment