If the website wants to send analytics after the user has finished the page, but the page may have been unloaded by then, sending an asynchronous request may not be sent by the browser at this point. So what should we do?
First, we might think of the beforeunload event. It enables the web page to trigger a confirmation dialog to ask the user if they want to leave the page. When the user enters unsaved data but accidentally closes the page or refreshes the page, the data will be lost. The use of beforeunload allows the user to confirm twice. Like the screenshot below:
However, clicking Leave or Cancel does not have a corresponding callback function for us to use, so it can only be used as a secondary confirmation.
Next is the unload event. This event is fired when the document is unloaded. It’s after the beforeunload event. If we send an asynchronous request in this event, as I said at the beginning, the browser may choose not to send this asynchronous request, or it may abort this request.
So can we send blocking synchronous XMLHttpRequest requests like below?
The answer is yes. But this prevents the document from being unloaded, which slows down the browser to navigate to the next page. The next page can’t avoid this, because the new page looks slow, even if it’s the previous page’s fault.
Similar to this: create an <img>
element and set its src, create a synchronous loop that does nothing for a few seconds, etc.
So these are not good solutions. Fortunately, browsers provide a targeted API for this — Navigator.sendBeacon()
This event allows profiling data to be sent asynchronously at the end of a page session without delay blocking the loading of the next page. And it guarantees that data is sent reliably.
Its syntax is this: navigator.sendBeacon(url [, data])
. data is an optional parameter, it can be an ArrayBuffer
, a TypedArray
, a DataView
, a Blob
, a string literal or object, a FormData
or a URLSearchParams
object.
Here is a simple example:
You can see that I use the visibilitychange
event here instead of the unload
or beforeunload
event. This is because both events are extremely unreliable. It is especially serious under mobile devices, such as when the user closes the browser application using the application manager of the mobile phone, when switching between different applications, etc.
Also, these two events are not compatible with modern browsers’ back/forward cache (bfcache). This is bad for performance. So the best event to signal the end of a user session is the visibilitychange
event. In browsers that don't support it, the next best option is pagehide
event, which is also not fired reliably, but which is bfcache-compatible.
Finally, let’s look at the type of data. When we use URLSearchParams
like above, the request header content-type
will be set to text/plain;charset=UTF-8
. Similarly, if we use FormData
, then the content-type
of the request header will be set to multipart/form-data
. But if we use Blob
, we can customize request headers and request content like this:
Do you know other ways? Feel free to share.
If you find this helpful, please consider subscribing to my newsletter for more insights on web development. Thank you for reading!