Using navigator.sendBeacon
I recently learned about navigator.sendBeacon
, a Web API method that lets you send data to a server when the user navigates away from your page, reloads, or closes their browser tab. (Or whenever.) It’s useful when you need to collect time-on-page and other analytics. Apparently it’s been around since 2018!
Some key things to know:
“Unlike requests made using XMLHttpRequest or the Fetch API, the browser guarantees to initiate beacon requests before the page is unloaded and to run them to completion.” Yay!
Requests made with
navigator.sendBeacon
will alwaysPOST
, are asynchronous and non-blocking, and do not look for a response — in fact servers are “encouraged to omit returning a response body for such requests (e.g. respond with204 No Content
)” per the spec. You can’tawait navigator.sendBeacon
to verify receipt or get any information back from the server.You can’t specify any custom headers or other request options.
You can use it anywhere of course, not just when the user is leaving your page. It can also be useful for other analytics events and pings where a server is listening silently to receive messages.
That’s about all! It just looks like this:
navigator.sendBeacon(
'https://example.com/analytics',
JSON.stringify({"foo": "bar"})
);
There are some other not-so-key things to know too, which were interesting when I stopped to think about it.
Content types
The second argument can be either “an ArrayBuffer, a TypedArray, a DataView, a Blob, a string literal or object, a FormData or a URLSearchParams object containing the data to send.”
Most of the time, it’s going to be simplest to just send JSON data in a string literal, per the above example. Or perhaps a FormData or URLSearchParams object depending on where you’re getting your data.
If you need to specify a content type for some reason, you can send a Blob, and put that in the Blob constructor:
navigator.sendBeacon(
'https://example.com/analytics',
new Blob(['{"foo":"bar"}'], {type:'application/json'})
);
It’s probably not worth doing that though! Just send a JSON-encoded string and let your server assume the content type.
Cross-origin requests
If you stick to the simple cases, your beacon will not use CORS and will not send a preflight OPTIONS
request even when sending to a cross-origin server.
The simple cases are sending a string literal, a FormData or URLSearchParams, or an equivalent type of Blob, i.e. application/x-www-form-urlencoded
, multipart/form-data
, or text/plain
.
In more complex cases a preflight request will be made. Presumably this makes the whole thing considerably less reliable for “send an event when the browser closes” and similar use cases. You probably don’t need or want this.
Credentials
The beacon will be sent with credentials mode “include” so your receiving server will have access to whatever cookies might be stored for its domain. (But since sendBeacon doesn’t get the server’s reply, you can’t use this to set a cookie with one beacon and keep sending it back with others.)
Finding beacons in Developer Tools
One small gotcha — when debugging your client-side code, you may want to look at these requests in the Chrome Developer Tools network tab. You won’t find them in the “Fetch/XHR” section — instead they’re classified as having Type: ping
and will show up in the “Other” section. (Or “All” of course.)
In retrospect this seems reasonable (I quoted docs up there that literally start by saying it’s not an XMLHttpRequest or the Fetch API) but it threw me off for a while.