Save your analytics data with the Beacon and PageVisibility APIs

If you want to ensure you capture some information about a user before they leave a particular section of your site or web app, you can use the Beacon and PageVisivibility web APIs to ensure that data is sent successfully to your own backend service.
The Problem with sending requests HTTP requests
One approach to sending analytical or logging information is to capture the event that occurs just before a user in a web browser navigates away from a page or closes their browser. We could send a network request to our backend service with some information like this:
There are a few problems with this:
- The browser won’t wait for the request to complete so it may get cancelled
- Using a technique to wait for the request to complete i.e. synchronously may impact the load time of the next page (if applicable)
- Some browsers will ignore requests made in the
unload
handler and others will not fire theunload
event at all (more on this later)
This is why using the Beacon API is a better choice if you want to ensure that that final request to your backend service completes, even if the user completely closes their browser.
How is the Beacon API different?
Using the Beacon API, those final requests sent by the page are guaranteed to be sent. They are also sent asynchronously so there are no side-effects on the next page loading time.
Because of these reasons, there is no need to write any additional code to ensure that those final requests are sent to your backend service and can therefore make your code simpler.
In short, the Beacon API will
- Reliably ensure that the data passed to the
sendBeacon
function is sent to the URL specified - Send requests asynchronously so won’t affect the load time of subsequent pages
Using the Beacon API
So how does the Beacon API work?
Well, it’s as simple as calling one function on the window.navigator
object. We could rewrite the previous example to use the Beacon API instead.
Although browser support is pretty good you might want to check if the sendBeacon
function exists on the navigator
object before trying to call it.
The sendBeacon
function will send a POST request to the URL specified as the first argument. The second argument is the data to be sent.
The format of the data can be anArrayBuffer
, ArrayBufferView
, Blob
, DOMString
, FormData
, or URLSearchParams
.
Don’t use the unload event
I mentioned earlier on that some browsers won’t fire the unload
event when a tab is closed or the user navigates to another page. This is particularly true of mobile browsers.
From this page of the Google Developers site:
Many developers treat the
unload
event as a guaranteed callback and use it as an end-of-session signal to save state and send analytics data, but doing this is extremely unreliable, especially on mobile! Theunload
event does not fire in many typical unload situations, including closing a tab from the tab switcher on mobile or closing the browser app from the app switcher.
So what is the best approach to send our Beacon requests when the page session has ended? Well, we can use the PageVisibility API to determine whether the user has moved away from the current tab.
Using the PageVisibility API
As the name suggests, the PageVisibility API can be used to determine whether a document is visible on the user’s display i.e. they have your tab in focus.
You can access the current state from the document.visibilityState
which will return one of either prerender
, hidden
, or most likely if you’re running it in your developer tools visible
. You can use this property to check at any particular point in your code whether the tab is currently visible to the user.
This isn’t that useful for our situation, but there is an event you can listen to for changes to the visibilityState
. So you can send a Beacon request when this changes.
So now when the user navigates away from the page, the visibilityChange
event will fire and we can safely send our analytics or logging information to our backend service.
A complete example
Let’s run through a complete example that you can run locally and experiment with.
First, set up a project with the dependencies for Express:
mkdir beacon-example
cd beacon-example
npm init -y
npm install express body-parser
mkdir public
touch public/index.html
touch app.js
Then, in the app.js
file, set up an Express server to receive our Beacon requests
Set the Express server running with:
node app.js
Then, inside the index.html
file contained within the public
folder we can add some JavaScript code to send the Beacon requests.
Whilst this is a bit crude hopefully you can understand how you could potentially collect information about how users are interacting with your page or web app and pass this back to your backend service using a sendBeacon
request.
You could extend this example by updating the events
array with information about additional events. For example, when the user clicks anywhere on the page:
If you head over to the terminal where you ran the backend Express service, you would see some output that looks like this:
Received Beacon: [
{ type: 'Session Start', time: 1603812976748 },
{ type: 'Click', x: 122, y: 151 },
{ type: 'Session End', time: 1603812979156 }
]
Conclusion
In this tutorial, we’ve taken a look at the problem with sending analytics and logging information to a backend service with a simple HTTP call and also why the unload
event shouldn’t be used to listen for the end of a session.
We now know that the Beacon API provides a safe and reliable way to pass this kind of data back to our own backend services without affecting subsequent page load times.
Also, we’ve seen how the PageVisibility API provides a safe event that can tell us when a user navigates away from a page, even on mobile devices.
Thanks for reading. If you found this useful, please follow me on Twitter (@codebubb) for more tutorials.