How to Reliably Send Data When Users Close a Page: sendBeacon vs fetch keepalive
This article explains why traditional fetch or XMLHttpRequest calls often fail during page unload, and demonstrates two modern browser APIs—navigator.sendBeacon and fetch with keepalive:true—that reliably transmit analytics or draft data without blocking the user experience.
We often need to send important data just before a user closes a page or tab.
Traditional asynchronous requests such as fetch or XMLHttpRequest are frequently aborted by the browser during unload events ( pagehide, unload) because the JavaScript environment disappears.
Why do normal requests fail?
When the unload handler fires, the browser may cancel any pending request since the page is being destroyed.
Developers used synchronous XMLHttpRequest, which blocks the main thread and freezes the UI.
Modern solution 1: navigator.sendBeacon()
navigator.sendBeacon()is a W3C API designed to send a small amount of data asynchronously and non‑blocking, even after the page is closed.
How it works
Calling sendBeacon() queues the request internally and returns immediately; the browser ensures the request is sent later, regardless of page unload.
Features
High reliability : guaranteed delivery by the browser.
Asynchronous, non‑blocking : does not delay page close.
Simple to use : straightforward API.
Data limits : only POST, no custom headers.
Code example
Send analytics data when the user leaves the page:
// Use 'pagehide' which is more reliable than 'unload'
window.addEventListener('pagehide', (event) => {
if (event.persisted) return;
const analyticsData = {
timeOnPage: Math.round(performance.now()),
lastAction: 'close_tab',
};
const blob = new Blob([JSON.stringify(analyticsData)], {
type: 'application/json; charset=UTF-8',
});
const success = navigator.sendBeacon('/log-analytics', blob);
if (success) {
console.log('Analytics log queued successfully.');
} else {
console.error('Failed to send analytics log.');
}
});Modern solution 2: fetch() with keepalive: true
Setting keepalive: true in the fetch init object tells the browser to continue the request after the page is unloaded.
How it works
The request is marked as “keep‑alive”, allowing its lifetime to outlive the page, similar to sendBeacon.
Features
High flexibility : supports various HTTP methods (POST, PUT, etc.) and limited custom headers.
Unified API : works with existing fetch code.
Cannot read response : like sendBeacon, the response cannot be processed after unload.
Code example
Automatically save a draft when the page is closed:
window.addEventListener('pagehide', (event) => {
if (event.persisted) return;
const draftContent = document.getElementById('editor').value;
if (!draftContent) return;
const draftData = {
content: draftContent,
timestamp: Date.now(),
};
// fetch with keepalive
fetch('/api/drafts/save', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(draftData),
keepalive: true,
});
console.log('Draft save request submitted.');
});How to choose?
If you only need to send simple analytics or log data, navigator.sendBeacon() is the most direct choice. For greater flexibility, such as using other HTTP methods or custom headers, fetch({ keepalive: true }) is preferable.
Both APIs let you deliver critical data without compromising user experience.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JavaScript
Provides JavaScript enthusiasts with tutorials and experience sharing on web front‑end technologies, including JavaScript, Node.js, Deno, Vue.js, React, Angular, HTML5, CSS3, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
