The problem with synchronous XHR on page close
Sites that need to flush analytics or other state when a user leaves often reach for a synchronous XMLHttpRequest() call to hold the page open until the data arrives. The intent is to prevent data loss, but the effect is a page that can hang for seconds while the user is trying to close it.
This is a pattern browsers are now moving away from. The XMLHttpRequest() specification already marks synchronous usage for deprecation and removal. Chrome 80 was the first to restrict synchronous calls in the beforeunload, unload, pagehide, and visibilitychange handlers when they fire during page dismissal. WebKit has since landed a matching change. If you rely on this technique, now is the time to plan a migration.
Temporary opt-outs
Chrome is giving developers a window to update their code before the restriction is permanent. Two temporary options are available:
- Origin trial: Add an origin-specific token to your page headers to re-enable synchronous
XMLHttpRequest()calls. This trial ends shortly before Chrome 89 ships, around March 2021. - Enterprise policy: Chrome enterprise customers can use the
AllowSyncXHRInPageDismissalpolicy flag, which expires at the same time.
Better ways to send data at dismissal
Regardless of which API you choose, the better practice is to avoid funneling all your data through a single unload-time request. Unload events are unreliable on modern browsers—especially mobile, where tabs can be closed in ways that never fire the unload handler. And even when the handler does run, blocking it with a synchronous request is a poor trade for the user. The two recommended replacements below both carry a 64 KB payload limit per context, a consequence of their specification requirements.
Fetch with keepalive
The Fetch API is the more robust option for server interactions, with a consistent interface across platform APIs. One of its options is keepalive, which tells the browser to continue the request even if the page that issued it is closing:
window.addEventListener('unload', { fetch('/siteAnalytics', { method: 'POST', body: getStatistics(), keepalive: true }); }
fetch() also gives you finer control over the request and returns a promise that resolves with a Response object. During a page dismissal you'll typically ignore the promise since the goal is to stay out of the way of the unload process.
sendBeacon()
sendBeacon() uses the Fetch API under the hood, which is why it shares the same 64 KB payload cap and continues after page unload. Its appeal is simplicity—it submits data in a single line of code:
window.addEventListener('unload', { navigator.sendBeacon('/siteAnalytics', getStatistics()); }
With fetch() supported across modern browsers, the path toward removing XMLHttpRequest() from the platform is clearer. Browser vendors agree it should go, but that will take time. Curbing its use during page dismissal is an important first step that improves the user experience for everyone.



