JavaScript Beacon API and navigator.sendBeacon Guide
This article provides an overview of the JavaScript Beacon API and
explains how the navigator.sendBeacon() method reliably
sends diagnostic and analytics data to a web server during page unload.
You will learn about the limitations of traditional asynchronous
requests during document termination, how the Beacon API solves these
problems by offloading network transmission to the browser background
process, and the proper syntax and best practices for implementing it in
your applications.
The Problem with Traditional Requests During Page Unload
Historically, developers relied on standard
XMLHttpRequest or the fetch() API inside
unload or beforeunload event listeners to send
analytics, metrics, or session data when a user navigated away from a
webpage.
This approach has major flaws: - Dropped Requests:
Asynchronous fetch or XMLHttpRequest calls are
often canceled immediately because the browser terminates the execution
context when the document unloads. - Degraded
Performance: To force data transmission, developers previously
used synchronous XMLHttpRequest calls. This blocks the main
thread, delays the loading of the next page, and creates a sluggish user
experience. Consequently, modern browsers have deprecated synchronous
requests during unload events.
What is the Beacon API?
The Beacon API was introduced by the W3C to solve the challenge of
sending telemetry and state data at the end of a user session. It
exposes a single method, navigator.sendBeacon(), which
schedules an asynchronous, non-blocking HTTP POST request managed
directly by the browser rather than the document’s script execution
environment.
Because the browser’s networking layer handles the request independently of the document’s lifecycle, the data is guaranteed to be transmitted even after the page has completely closed, without delaying the user’s navigation.
How
navigator.sendBeacon() Works
When navigator.sendBeacon() is invoked, the browser
queues the request in an internal buffer. The method immediately returns
a boolean value: - true if the browser successfully
accepted and queued the request for delivery. - false if
the request could not be queued (e.g., if the payload exceeds size
limits).
The browser then transmits the data over HTTP POST in the background when it is optimal to do so, requiring no callbacks or response handling.
Syntax and Parameters
navigator.sendBeacon(url, data);url: The URL of the server endpoint receiving the POST request.data(optional): The payload to be sent. Supported data types includeArrayBuffer,TypedArray,DataView,Blob,DOMString(text),FormData, andURLSearchParams.
Practical Code Example
function sendAnalyticsData() {
const url = "/api/analytics";
const payload = JSON.stringify({
event: "session_end",
timestamp: Date.now(),
timeSpentOnPage: performance.now()
});
const blob = new Blob([payload], { type: "application/json" });
if (navigator.sendBeacon) {
navigator.sendBeacon(url, blob);
} else {
// Fallback using fetch with keepalive
fetch(url, { method: "POST", body: payload, keepalive: true });
}
}
// Recommended lifecycle event for modern browsers
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
sendAnalyticsData();
}
});Best Practices and Limitations
- Use
visibilitychangeInstead ofunload: Theunloadandbeforeunloadevents are unreliable, especially on mobile browsers where tabs are frequently discarded or suspended. Thevisibilitychangeevent (checking fordocument.visibilityState === 'hidden') is the most reliable event for capturing the end of a session. - Keep Payloads Small: Browsers typically impose a total quota on queued beacon data (often 64 KB across all active beacons) to prevent excessive memory usage.
- Fire-and-Forget Nature: The Beacon API does not provide a mechanism to handle server responses. It is intended strictly for one-way logging, telemetry, and analytics.
- Alternative: Modern
fetch()supports the{ keepalive: true }option, which offers similar background persistence capabilities while allowing custom headers and other standard Fetch API configurations.