JavaScript Beacon API for Reliable Analytics Telemetry

The Beacon API is a specialized web API designed to send small amounts of analytical, diagnostic, and telemetry data from the browser to a web server asynchronously and reliably. Unlike standard HTTP requests that can be abruptly canceled when a user navigates away or closes a tab, the Beacon API guarantees that the browser queues and delivers the data in the background without delaying page unloads or degrading user experience.

The Problem with Traditional Data Transmission

Web analytics often capture metrics right when a user finishes a session or leaves a page. Historically, developers relied on XMLHttpRequest (XHR) or the fetch() API inside event listeners like unload or beforeunload.

These traditional methods introduce significant drawbacks during page transitions: * Dropped Requests: Asynchronous fetch or XHR requests are frequently aborted by the browser when the page unloads, resulting in lost analytics data. * UI Freezing: Using synchronous XHR forces the browser to delay the navigation until the server responds, creating a sluggish and frustrating experience for the user. * Keepalive Limitations: While fetch() with the { keepalive: true } flag addresses some of these issues, it has varying support edge cases and requires more verbose error handling.

How the Beacon API Solves the Problem

The Beacon API resolves these issues by handing off the data transmission process entirely to the browser. Once invoked, the browser queues the request and transmits the data over HTTP POST in the background, independent of the originating document’s lifecycle.

The core implementation relies on the navigator.sendBeacon() method:

navigator.sendBeacon(url, data);

Key Benefits of navigator.sendBeacon()

Implementing Beacon with visibilitychange

For modern web applications, the most reliable event to send telemetry is visibilitychange, rather than the deprecated unload or beforeunload events. Mobile browsers frequently discard background tabs without firing the unload event.

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    const analyticsData = JSON.stringify({
      event: 'page_exit',
      timeSpent: performance.now(),
      path: window.location.pathname
    });

    const blob = new Blob([analyticsData], { type: 'application/json' });
    navigator.sendBeacon('/api/telemetry', blob);
  }
});

Constraints and Best Practices