The fetchLater API Origin Trial

Brendan Kenny
Brendan Kenny

It's common for web pages to need to send data (or "beacon") back to their server—think analytics data for a user's current session, as an example. For developers, this requires a balancing act: reducing constant, possibly redundant, requests without risking missed data if the tab was closed or the user navigated away before a beacon can be sent.

Traditionally, developers have used pagehide and visibilitychange events to catch the page as it unloads, and then use navigator.sendBeacon() or a fetch() with keepalive to beacon data. However, both of these events have difficult corner cases that differ based on the user's browser, and sometimes the events never arrive at all—especially on mobile.

fetchLater() is a proposal to replace this complexity with a single API call. It does exactly as its name suggests: it asks the browser to ensure a request is made at some point in the future, even if the page is closed or the user navigates away.

fetchLater() is available in Chrome for testing with real users behind an origin trial starting in version 121 (released in January 2024), running until 3rd September 2024.

The fetchLater() API

const fetchLaterResult = fetchLater(request, options);

fetchLater() takes two arguments, generally identical to those of fetch():

  • The request, either a string URL or a Request instance.
  • An optional options object, which extends the options from fetch() with a timeout called activateAfter.

fetchLater() returns a FetchLaterResult, currently containing only a single read-only property activated, which will be set to true when "later" has passed and the fetch has been made. Any response to the fetchLater() request is discarded.

request

The simplest usage is a URL by itself:

fetchLater('/endpoint/');

But, just like fetch(), a large number of options can be set on a fetchLater() request, including custom headers, credentials behavior, a POST body, and an AbortController signal to potentially cancel it.

fetchLater('/endpoint/', {
  method: 'GET',
  cache: 'no-store',
  mode: 'same-origin',
  headers: {Authorization: 'SUPER_SECRET'},
});

options

The options object extends fetch()'s options with a timeout, activateAfter, in case you want to fire the request after the timeout or when the page is unloaded, whichever comes first.

This lets you decide the tradeoff between getting data at the absolute last possible moment or when it's more timely.

For example, if you have an app your users usually keep open for the entire work day, you might want to have a timeout of an hour to ensure more granular analytics while still guaranteeing a beacon if the user exited at any time before that hour was up. A new fetchLater() can then be set up for the next hour of analytics.

const hourInMilliseconds = 60 * 60 * 1000;
fetchLater('/endpoint/', {activateAfter: hourInMilliseconds});

Example use

One issue when measuring Core Web Vitals in the field is that any of the performance metrics could change until the user actually leaves a page. For example, larger layout shifts could happen at any time, or the page could take even longer to respond to an interaction.

However, you don't want to risk losing all performance data due to buggy or incomplete beaconing on page unload. It's a perfect candidate for fetchLater().

In this example, the web-vitals.js library is used to monitor the metrics, and fetchLater() is used to report the results to an analytics endpoint:

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

const queue = new Set();
let fetchLaterController;
let fetchLaterResult;

function updateQueue(metricUpdate) {
  // If there was an already complete request for whatever
  // reason, clear out the queue of already-sent updates.
  if (fetchLaterResult?.activated) {
    queue.clear();
  }

  queue.add(metricUpdate);

  // JSON.stringify used here for simplicity and will likely include
  // more data than you need. Replace with a preferred serialization.
  const body = JSON.stringify([...queue]);

  // Abort any existing `fetchLater()` and schedule a new one with
  // the update included.
  fetchLaterController?.abort();
  fetchLaterController = new AbortController();
  fetchLaterResult = fetchLater('/analytics', {
    method: 'POST',
    body,
    signal: fetchLaterController.signal,
    activateAfter: 60 * 60 * 1000, // Timeout to ensure timeliness.
  });
}

onCLS(updateQueue);
onINP(updateQueue);
onLCP(updateQueue);

Every time a metric update comes in, any existing scheduled fetchLater() is canceled with an AbortController and a new fetchLater() is created with the update included.

Try out fetchLater()

As stated, fetchLater() is available in an origin trial until Chrome 126. See "Get started with origin trials" for background information on origin trials

For local testing, fetchLater can be enabled with the Experimental Web Platform features flag at chrome://flags/#enable-experimental-web-platform-features. It can also be enabled by running Chrome from the command line with --enable-experimental-web-platform-features, or the more targeted --enable-features=FetchLaterAPI flag.

If you use it on a public page, make sure to feature detect by checking if the global fetchLater is defined before using it:

if (globalThis.fetchLater) {
  // Set up beaconing using fetchLater().
  // ...
}

Feedback

Developer feedback is essential to getting new web APIs right, so please file issues and feedback on GitHub.

More information