What Is the Beacon API? navigator.sendBeacon()
The Beacon API lets a page send one last async request as it unloads, without blocking navigation or racing the browser's page teardown.
The Beacon API is a browser API for sending a small asynchronous HTTP request that’s guaranteed to be dispatched even as the page is being unloaded — closed, navigated away from, or backgrounded. Its single method, navigator.sendBeacon(), exists to solve a problem that ordinary requests handle badly: firing off analytics or logging data right as the user leaves.
The problem it solves
Before the Beacon API, sending a final request on page unload meant using a synchronous XMLHttpRequest inside an unload or beforeunload handler. Synchronous requests block the page from actually unloading until the request finishes or times out, which delays navigation and hurts the experience for the next page the user is trying to reach. Asynchronous requests fired the same way were unreliable in the other direction: the browser is free to cancel in-flight requests once a page starts tearing down, so an async analytics call in an unload handler frequently just never arrived.
sendBeacon() closes that gap. It queues the request with the browser itself rather than tying it to the page’s own execution context, so the browser can guarantee delivery — typically via a background HTTP request — even after the page that initiated it is gone.
How it works
The call itself is simple: navigator.sendBeacon(url, data). The url is the endpoint receiving the data, and data can be a string, a Blob, FormData, or a URLSearchParams object. It returns a boolean immediately — true if the browser successfully queued the request, false if it didn’t (for example, if the payload exceeds the browser’s size limit for beacons).
window.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
navigator.sendBeacon("/analytics", JSON.stringify({ event: "page_exit" }));
}
});
Two details matter for correct use. First, sendBeacon() always sends a POST request — there’s no way to configure the method. Second, the call is fire-and-forget: your code gets no response body, no status code, and no way to know whether the server actually processed the request, only whether the browser accepted it into its send queue. That trade-off is intentional — it’s what makes the request possible to fire reliably at the exact moment a page is disappearing.
Beacon API vs fetch with keepalive
The fetch() API’s keepalive option was added later and covers similar ground, which raises the question of which one to reach for.
navigator.sendBeacon() | fetch(url, { keepalive: true }) | |
|---|---|---|
| HTTP method | Always POST | Any method |
| Response access | None — fire-and-forget | Full response, if the request completes before teardown |
| Request headers | Limited — set by the browser based on payload type | Fully customizable |
| Cancellation | Not cancellable once sent | Can be aborted via AbortController while still running |
| Best for | Simple unload-time telemetry | Anything needing custom headers, methods, or a response |
For a plain “record that the user left” event, sendBeacon() is simpler and has been reliably supported for longer. For anything needing custom request headers, a method other than POST, or access to the response, fetch with keepalive is the better fit — just be aware it inherits the same payload-size limits browsers apply to keepalive requests during unload.
Common use cases
The Beacon API’s core use case is analytics: recording that a page was viewed for a certain duration, that a user abandoned a form, or that a session ended, all of which naturally happen right as the page is closing. It’s also used for error logging that needs to flush before the page disappears, and for batching up smaller telemetry events and sending them as a single beacon on visibility change rather than making a request per event. It pairs naturally with the same instrumentation used to track Core Web Vitals, since some of those metrics — like layout shift — only finish accumulating right before the page unloads.
Limitations
Beacons aren’t a general-purpose replacement for ordinary requests. The payload size is capped by the browser (small, on the order of tens of kilobytes), there’s no way to read a response, and because the method is always POST, it isn’t suitable for anything that needs a different verb. It’s also not the right tool for real-time bidirectional communication — for that, look at server-sent events or WebSockets instead, which stay connected for the life of the page rather than firing a single request at the end of it.
The takeaway
The Beacon API exists for exactly one job: sending a small, fire-and-forget request that the browser guarantees to deliver even as the page unloads, which ordinary synchronous or asynchronous requests couldn’t do reliably. Reach for navigator.sendBeacon() for simple exit-time analytics and logging; reach for fetch with keepalive when you need a custom method, headers, or the option to inspect a response. Either way, don’t use a synchronous request in an unload handler — it blocks navigation for no benefit the Beacon API doesn’t already provide without the cost.
Keep reading
Takina · · 3 min read What Is Fetch Priority? The fetchpriority Attribute
fetchpriority lets you tell the browser which resources matter most, overriding its default heuristics to load critical assets sooner.
Takina · · 5 min read CSS will-change Explained: Compositing and Performance
The CSS will-change property hints the browser to prepare an element for an upcoming change, moving it to its own compositor layer. When to use it and when not to.
Takina · · 4 min read WebP vs AVIF vs JPEG: Choosing an Image Format
WebP, AVIF, and JPEG trade off compression, browser support, and encode speed differently. Which format to use where, and how to serve fallbacks safely.