In this module, you will build a minimal analytics beacon script in roughly 20 lines of JavaScript. By the end, you will understand how analytics data travels from the browser to a server and why navigator.sendBeacon() exists.
Run: Open test.html in your browser. Open DevTools Network tab. Click the link to trigger the beacon.
Most of the HTTP traffic you have studied so far flows in one direction: the browser requests content from a server. Analytics beacons reverse that relationship. A beacon is an outbound data transmission — the browser collects information about the user's experience and sends it to an analytics endpoint.
The server never asks for this data. The browser volunteers it. Think of it as a field report: the page loads, the user interacts with it, and at some strategic moment the browser phones home with a summary of what happened.
The beacon carries a JSON payload describing what happened — which page was viewed, when, where the user came from, and any performance or interaction data you choose to include.
navigator.sendBeacon() is a browser API designed specifically for analytics. It solves a problem that plagued web analytics for years: how do you reliably send data when the user is leaving the page?
Key properties of sendBeacon():
How does it compare to other approaches?
| Method | Survives Unload | Blocks Navigation | Response Access |
|---|---|---|---|
sendBeacon() |
Yes | No | No |
fetch() |
No* | No | Yes |
fetch(keepalive) |
Yes | No | Yes |
XMLHttpRequest |
No | Can block | Yes |
| Image pixel | Yes | No | No |
fetch with keepalive: true also survives unload, but it has a 64KB payload limit and less browser support history. sendBeacon() remains the most reliable and widely supported option for analytics payloads.
Let us build collector-v1.js step by step.
Every analytics beacon needs a payload — the data it carries. At minimum, you want to know what was viewed, where the user came from, and when it happened:
const payload = {
url: window.location.href,
title: document.title,
referrer: document.referrer,
timestamp: new Date().toISOString()
};
Why these fields?
url — The full URL of the current page. This is the most fundamental piece of analytics data: which page was viewed.title — The page title from the <title> element. More human-readable than a URL, useful for reports and dashboards.referrer — Where the user came from. If they clicked a link on another site (or another page on your site), document.referrer contains that URL. Empty string if the user typed the URL directly or the referrer was suppressed.timestamp — When the event occurred, in ISO 8601 format. Server-side timestamps are also useful, but the client timestamp captures the user's actual moment of interaction.With the payload assembled, sending it is three lines:
const endpoint = '/collect';
const blob = new Blob([JSON.stringify(payload)], { type: 'application/json' });
navigator.sendBeacon(endpoint, blob);
Why Blob? sendBeacon() can accept a Blob, FormData, URLSearchParams, or plain string. Using a Blob with an application/json content type lets the server parse the request body as JSON. Without the Blob wrapper, the content type would default to text/plain, and many server frameworks would not automatically parse the body.
You have two main choices for when to send the beacon:
// Option A: On page load (captures arrivals)
document.addEventListener('DOMContentLoaded', () => {
sendBeacon();
});
// Option B: On page unload (captures departures, includes time-on-page)
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
sendBeacon();
}
});
Option A tells you that a user arrived. Option B tells you that a user left. For a minimal collector, Option A is simpler. In later modules, we will combine both to calculate time-on-page.
unload event is unreliable on mobile browsers — many mobile browsers never fire it because pages are suspended rather than closed. The visibilitychange event fires reliably across desktop and mobile when a page becomes hidden (tab switch, navigation, app switch). Using unload also prevents the browser from storing the page in the back-forward cache (bfcache), which hurts performance for users who navigate back.
Here is the full collector-v1.js:
// collector-v1.js — Minimal Analytics Beacon
(function() {
'use strict';
const endpoint = '/collect';
function sendBeacon() {
const payload = {
url: window.location.href,
title: document.title,
referrer: document.referrer,
timestamp: new Date().toISOString(),
type: 'pageview'
};
const blob = new Blob(
[JSON.stringify(payload)],
{ type: 'application/json' }
);
if (navigator.sendBeacon) {
navigator.sendBeacon(endpoint, blob);
} else {
// Fallback for older browsers
fetch(endpoint, {
method: 'POST',
body: blob,
keepalive: true
});
}
}
// Fire on page load
if (document.readyState === 'complete') {
sendBeacon();
} else {
window.addEventListener('load', sendBeacon);
}
})();
Note the structure:
(function() { ... })(). This prevents any variables from leaking into the global scope, which could conflict with the page's own JavaScript.'use strict' — Enables strict mode for better error checking.if (navigator.sendBeacon) before using it, with a fetch fallback for older browsers.readyState check — If the script loads after the page is already complete, we fire immediately rather than waiting for an event that already happened.Here is a sample JSON payload that this script would send:
{
"url": "https://example.com/products/widget",
"title": "Widget Product Page",
"referrer": "https://example.com/",
"timestamp": "2026-01-15T08:30:00.000Z",
"type": "pageview"
}
This is the minimum viable analytics payload. With just these five fields, you can build:
urlreferrer to see where visitors come fromtimestamp data to see traffic patterns over timeIn subsequent modules, we will enrich this payload with browser details (Module 02), performance timings (Module 05), and more.
collector.js file (565 lines) that ships with this course does everything we will build across all 10 modules. In this module, we have built the foundation that collector.js's reportPerf() function sits on top of — the basic mechanism of collecting data and sending it to a server. See analytics-overview.html Sections 2 (Analytics Stack) and 3 (Collection Methods) for the theory behind what we just built.
navigator.sendBeacon() is purpose-built for analytics datafetch without keepalive)visibilitychange over unload for reliable firing