navigator.sendBeacon
navigator.sendBeacon sends a small HTTP POST request reliably during page unload. The browser guarantees delivery without blocking navigation — making it ideal for fire-and-forget data like analytics pings.
Browser support: Widely available across all modern browsers.
Use cases
- Analytics pings (page views, click tracking)
- Diagnostics and error reporting
- Session-end data (time on page, scroll depth)
Example
The modern best practice is to send beacons on visibilitychange instead of unload (which is unreliable on mobile):
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
const data = JSON.stringify({ event: "session_end", duration: 42000 });
navigator.sendBeacon("/analytics", new Blob([data], { type: "application/json" }));
}
});
Comparison with fetch + keepalive
You can achieve similar reliability with fetch, plus gain more control:
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
fetch("/analytics", {
method: "POST",
body: JSON.stringify({ event: "session_end", duration: 42000 }),
headers: { "Content-Type": "application/json", "X-Custom": "value" },
keepalive: true,
priority: "low",
});
}
});
| Feature | sendBeacon | fetch + keepalive |
|---|---|---|
| Custom headers | No (Content-Type inferred from body) | Yes |
| Response access | No | Yes |
| Priority control | No | Yes (priority option) |
| Simplicity | One-liner | More verbose |
| HTTP methods | POST only | Any |
When to use which
Use sendBeacon for simple fire-and-forget POST requests — typically analytics or diagnostics. Prefer fetch with keepalive: true and priority: "low" when you need custom headers, response access, priority control, or a method other than POST.
Limitations
- 64 KiB payload cap — shared quota across all in-flight keepalive requests (both
sendBeaconandfetch+keepalive) - POST only — no other HTTP methods
- No access to the response body
- No custom headers beyond what the body type implies for Content-Type