Using the Beacon API
We need to capture performance data as the user is leaving the page. The Beacon gives us a reliable way to do that. Users are so annoying! There they go, leaving the page before we can save the data to the API. Back in the old days, we would attach a window.unload
handler and try to send an AJAX request, but this was slow and unreliable.
Enter the Beacon API to give us a better way.
The Beacon provides a reliable way to send a small amount of data after the page has been unloaded. It’s not bound to the lifetime of the page and it doesn’t slow down the user.
Here is the source for the beacon demo on GitHub.
Using sendBeacon
The Beacon is exposed on the navigator
object as sendBeacon
. Browser compatibility is really good, except of course for Internet Explorer. Still, we should do some feature detection to make sure it works so our JavaScript doesn’t throw an error.
If your JavaScript throws errors, as most JavaScript does, check out our other product, TrackJS, the fastest and easiest way to find and fix bugs on your production websites.
We call sendBeacon
with the URL and the text string to be sent. The browser handles the rest. We still want to attach our call to sendBeacon
to the window
unload
event, so that we queue our data to be sent as the page is closing.
Here is a simple example:
Showing Beacon Requests in DevTools
Running this code, you may not see the beacon requests in your devTools. This is because the beacon requests often happen after the page has unloaded, and the devTools history has been cleared. Turn on “Persist Logs” in your network panel, and you should see the beacon responses.
In Chrome, there is a longstanding bug where beacon requests are always shown in “(Pending)” status. This is expected, and it doesn’t mean your beacon is broken :).
Changing the Content-Type
If you examine the network requests for the beacon, you’ll see that the requests are being sent with a Content-Type
header of “text/plain”, which isn’t strictly correct because we are sending a JSON string. Many server technologies will automatically detect and parse the content if sent with the correct headers, so it would be nice if we could correct the Content-Type
to “application/json”.
In the MDN page on beacon, it shows an example using a Blob
to set the Content-Type
:
Unfortunately, this DOES NOT WORK.
Setting the Content-Type
requires this request to include the Cross-Origin Resource Sharing (CORS) headers, which are a Pain-In-The-Ass (PITA).
Not only do the CORS headers need to be properly configured, but we would need to ensure that a request has been sent to our API before we attempt to send the beacon in order to handle the CORS preflight request.
Even if you jump through all these hoops, it’s not going to work in Chrome anyway. As of Chrome 39, this behavior has been disabled due to a security concern.
So stick with text/plain and handle the JSON serialization yourself.
Dealing with Safari
Safari needs to be different. Safari does not always trigger the unload
event due to performance concerns, especially when navigating to new domains and on mobile devices.
Instead, Safari will trigger the pagehide
event each time the page loses focus, including on navigation. Compatibility of the pagehide
event is not well documented, but it worked for me on Chrome 81, Firefox 76, and Safari 13.
We can modify our code to listen to either event, which should cover all the browser cases.
Wrapping Up
The Beacon API lets us send data to our API after the user has closed the page. At Request Metrics, we use this to send out final performance data about the page. Subscribe to our YouTube channel where we’ve documented our journey building Request Metrics.