What does the keepalive: true option on the browser's fetch() give you that navigator.sendBeacon does not, and what limit does the browser place on keepalive request bodies?
answer
- same lifetime guarantee, more control
- headers, method, credentials, response
- one shared in-flight pool
- TypeError versus false
- no streaming body with keepalive
basics
~20 sfetch with keepalive gives the same survive-the-page guarantee as sendBeacon while letting you choose the method, set headers such as Content-Type, control credentials, and read the response. The cost is a shared in-flight body budget of about 64 KiB per document.
solid answer
~50 sBoth give the same delivery guarantee — the browser finishes the request after the document is gone — so choose between them on control, not on reliability. `fetch(url, { keepalive: true })` lets you pick the method, set arbitrary headers such as `Content-Type: application/json` or `Authorization`, choose `credentials` and `mode`, and it returns a promise with a real `Response` you can read if the page is still alive. `navigator.sendBeacon()` is POST-only, header-less, and gives you a boolean. The limit is the same for both: the total body size of all in-flight keepalive requests for a document is capped at roughly 64 KiB. Cross it and `fetch` rejects with a `TypeError` while `sendBeacon` returns `false`. Keepalive also cannot carry a `ReadableStream` body. Support is now broad on evergreen browsers, though Firefox only shipped it in version 133.
code
javascript · 15 linesasync function sendExit(payload) {
const body = JSON.stringify(payload);
try {
const res = await fetch('/collect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
keepalive: true,
});
return res.ok;
} catch (err) {
// TypeError here usually means the keepalive body budget was exceeded.
return navigator.sendBeacon('/collect', body.slice(0, 60000));
}
}go deeper
Know that fetch has a keepalive option and that it exists so a request survives the page closing, and that sendBeacon is the simpler POST-only alternative. Do not worry about the exact byte budget.
Explain the concrete gains — method, headers, credentials, a readable Response — and state the roughly 64 KiB in-flight body budget shared by beacons and keepalive fetches, plus the differing failure signals.
Show you keep exit payloads inside the budget by design: incremental sends during the session, small terminal payloads, and a defined behaviour when the send is refused rather than silent data loss.
Decide the house rule on exit telemetry across teams — what may be sent at exit at all, what the payload ceiling is, and how the budget is policed so one feature's blob does not starve another's beacon.
## The same guarantee, different ergonomics `keepalive` is a boolean in the `fetch()` init object. Setting it tells the browser to keep the request alive independently of the document that started it, which is precisely what `navigator.sendBeacon()` does under the hood. Delivery-wise the two are equivalent. Everything else about them differs. ```js fetch('/collect', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Client-Build': buildId }, body: JSON.stringify(payload), credentials: 'include', keepalive: true, }); ``` What that buys you over a beacon: - **Any method.** A beacon is always POST. With `fetch` you can `PUT` to a per-session key, which makes retries idempotent on the server. - **Real headers.** A beacon derives its `Content-Type` from the body type and offers no way to set anything else. With `fetch` you send `application/json`, an `Authorization` header, a schema version, a trace id. - **Explicit credentials and mode.** You choose `omit`, `same-origin` or `include`, and you choose `cors` or `no-cors`, instead of taking the beacon's fixed behaviour. - **A readable response.** `fetch` returns a promise resolving to a `Response`. If the document is still alive — the common case for a send at `visibilitychange` to hidden, since the user may come back — you can check `response.ok`, read a server-assigned id, or log a rejection. What you give up is that `fetch` is not fire-and-forget: it produces a promise, and an unhandled rejection during teardown is noise at best. Attach a `.catch()` even if the handler does nothing. ## The 64 KiB budget Keepalive requests are a resource the browser must fund after your page stops existing, so they are rationed. The cap is on the **total body size of all in-flight keepalive requests for the document**, and it is approximately 64 KiB. It is a shared pool: beacons and keepalive fetches draw on the same budget, and a request only returns its share once it completes. The failure modes differ by API, which is a favourite interview detail: - `fetch(..., { keepalive: true })` **rejects with a `TypeError`** when the body would exceed the remaining budget. - `navigator.sendBeacon()` **returns `false`**. Neither tells you it was a size problem in a machine-readable way, so treat both as "the payload was too big or the pool is full". The practical discipline is to keep exit payloads small — a handful of counters and ids, not a session replay — and to send incrementally during the session rather than accumulating one large terminal blob. If you genuinely need to ship kilobytes at exit, compress before sending, or write the payload to storage and upload it on the next visit. One more restriction: a keepalive request cannot have a `ReadableStream` body. Streaming uploads need a live document by definition, so the two features are mutually exclusive. ## Choosing between them Reach for `sendBeacon` when the payload is small, POST is fine, `text/plain` or form encoding is fine, and you truly do not care about the response. It is the simplest thing that works and it cannot leave a dangling promise. Reach for `fetch` with `keepalive` when you need JSON with the correct `Content-Type`, an auth header, a non-POST method, explicit credentials, or the response body. It is also the natural fallback when `sendBeacon` returns `false` and you want the `TypeError` to tell you why. ## Version note As of this writing, `keepalive` is supported across the evergreen browsers, but Firefox only shipped it in Firefox 133 (late 2024), well after Chrome and Safari. If you must support older Firefox builds, feature-detect it and fall back to `sendBeacon` — which has had much longer, broader support — rather than assuming the flag is honoured. An unsupported `keepalive` is silently ignored, not an error, so the request degrades to an ordinary fetch that teardown will cancel.
- If both survive the page, why would anyone still use sendBeacon?Because it cannot go wrong at teardown. It returns a boolean synchronously, so there is no promise left dangling in a document that is disappearing and no unhandled rejection to worry about. For a small POST where you never read the response, it is simply the smaller-surface tool.
- What exactly counts against the 64 KiB budget?The request bodies of all keepalive requests currently in flight for that document, beacons included — it is one shared pool, not a per-request cap. A request releases its share only when it completes, so several beacons fired in quick succession can exhaust it together.
- Can you upload a stream with keepalive set?No. A ReadableStream body is not allowed on a keepalive request. Streaming an upload requires a live document producing chunks, which is exactly what keepalive is designed to outlive, so the two features cannot be combined.
saying these in an interview costs you the question
- Thinks keepalive delivers better than sendBeacon
- Claims the 64 KiB cap is per request only
- Expects an oversized keepalive fetch to resolve normally
- Tries to stream an upload with keepalive set
- Assumes the flag is honoured by every browser version