In Module 01 we sent a minimal beacon with just a URL, title, and timestamp. That tells you what pages are visited, but nothing about who is visiting or how they experience your site. In this module, we collect technographic data — the technical profile of each visitor's browser, device, screen, network, and preferences — and attach it to every beacon.
Run: Open test.html in your browser.
Technographic data describes the user's technical environment: what browser they run, what operating system, how large their screen is, what network conditions they face, and what hardware capabilities they have. This is the data that helps developers understand their actual audience — not just what they assume.
For example: if you discover that 15% of your users are on slow-2g or 2g connections, that changes how you build. You might defer heavy images, reduce JavaScript bundles, or enable a data-saver mode. Without technographic data, you are guessing.
The browser exposes this information through several APIs on the navigator, window, and screen objects. We will collect it all into a single getTechnographics() function.
The most basic identification: what browser is this, and what language does the user prefer?
const ua = navigator.userAgent;
const language = navigator.language;
The User-Agent string contains browser name, version, operating system, and device information — all packed into a single (often messy) string. For example:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
navigator.userAgentData), but the legacy navigator.userAgent string remains widely available and is still the most practical approach for analytics collection today.
Two different measurements matter here: the viewport (what the user sees right now) and the screen (the physical display).
const viewport = {
width: window.innerWidth,
height: window.innerHeight
};
const screen = {
width: window.screen.width,
height: window.screen.height,
pixelRatio: window.devicePixelRatio
};
The viewport changes when the user resizes their browser window. The screen dimensions are fixed for the device. devicePixelRatio tells us if they are on a Retina/HiDPI display — a value of 2 means retina (each CSS pixel maps to 4 physical pixels), while 1 means a standard-density display. Values of 3 are common on modern phones.
Two APIs reveal the device's computing power:
const hardware = {
cores: navigator.hardwareConcurrency || 0,
memory: navigator.deviceMemory || 0
};
0.25, 0.5, 1, 2, 4, 8. Not all browsers support it — Safari and Firefox do not. Always feature-detect with a fallback to 0.
The Network Information API provides estimates of the user's connection quality:
function getNetworkInfo() {
if (!('connection' in navigator)) return {};
const conn = navigator.connection;
return {
effectiveType: conn.effectiveType, // 'slow-2g', '2g', '3g', '4g'
downlink: conn.downlink, // Mbps estimate
rtt: conn.rtt, // Round-trip time in ms
saveData: conn.saveData // User enabled data saver
};
}
The effectiveType is particularly useful — it classifies the connection into one of four categories based on observed performance, not the underlying technology. A user on Wi-Fi with poor signal might report 2g. The saveData flag indicates the user has explicitly opted into reduced data usage.
if ('connection' in navigator) before accessing. Your collector must gracefully handle its absence by returning an empty object.
Modern browsers expose user preferences that can inform how you serve content:
const preferences = {
colorScheme: window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark' : 'light',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
cookiesEnabled: navigator.cookieEnabled
};
The color scheme preference tells you whether the user has enabled dark mode at the OS level. The timezone reveals their geographic region (e.g., America/Los_Angeles). And cookieEnabled tells you whether your collector can rely on cookie-based session tracking — or needs an alternative.
Analytics needs to group page views into sessions — a sequence of pages viewed by the same user in a single sitting. The simplest approach: generate a random ID and store it in sessionStorage.
function getSessionId() {
let sid = sessionStorage.getItem('_collector_sid');
if (!sid) {
sid = Math.random().toString(36).substring(2) + Date.now().toString(36);
sessionStorage.setItem('_collector_sid', sid);
}
return sid;
}
This ID persists across page navigations within the same tab, but clears automatically when the tab (or browser) closes. No cookies, no cross-site tracking, no consent banner needed. The tradeoff: opening a new tab starts a new session, and you cannot track users across visits.
The ID itself combines Math.random() (for uniqueness) with a base-36 timestamp (for rough ordering). It is not cryptographically secure, but it does not need to be — it is a session label, not a secret.
The difference is intent and aggregation: analytics aggregates data to understand populations ("40% of our users are on mobile"), while fingerprinting correlates data to track individuals ("this specific combination of screen size, timezone, fonts, and GPU identifies user #4827").
In this tutorial, we collect technographic data for legitimate analytics. Be aware that the same techniques can be misused. See analytics-overview.html Section 7 (Fingerprinting) for more on this distinction.
Now we combine everything into a single function that returns the full technographic profile:
function getTechnographics() {
// Network info (feature-detected)
let networkInfo = {};
if ('connection' in navigator) {
const conn = navigator.connection;
networkInfo = {
effectiveType: conn.effectiveType,
downlink: conn.downlink,
rtt: conn.rtt,
saveData: conn.saveData
};
}
return {
// Browser identification
userAgent: navigator.userAgent,
language: navigator.language,
cookiesEnabled: navigator.cookieEnabled,
// Viewport (current browser window)
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight,
// Screen (physical display)
screenWidth: window.screen.width,
screenHeight: window.screen.height,
pixelRatio: window.devicePixelRatio,
// Hardware
cores: navigator.hardwareConcurrency || 0,
memory: navigator.deviceMemory || 0,
// Network
network: networkInfo,
// Preferences
colorScheme: window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark' : 'light',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
};
}
Every property uses feature detection or provides a safe fallback. The function returns a plain object — no side effects, no async, no dependencies.
Our collector-v2.js extends the Module 01 collector by adding technographics and a session ID to every payload:
function collect() {
const payload = {
url: window.location.href,
title: document.title,
referrer: document.referrer,
timestamp: new Date().toISOString(),
type: 'pageview',
session: getSessionId(),
technographics: getTechnographics()
};
const blob = new Blob([JSON.stringify(payload)], { type: 'application/json' });
const endpoint = 'https://example.com/collect';
if (navigator.sendBeacon) {
navigator.sendBeacon(endpoint, blob);
}
console.log('[collector-v2] payload:', payload);
}
Compare this to the Module 01 payload — we have added two new fields: session (a stable ID for grouping page views) and technographics (the full technical profile). The beacon delivery mechanism is identical.
getTechnographics():
initialBrowserData() (line 209) — UA, viewport dimensions, language, cookie statusgetNavigatorInfo() (line 125) — hardware concurrency, device memory, device classificationgetNetworkInformation() (line 185) — effective type, downlink, RTT, save-data flagThe reference implementation also computes derived values like isLowEndDevice and isLowEndExperience based on hardware and network thresholds. We will revisit device classification in later modules.
See analytics-overview.html Section 4 (What Can Be Collected) and Section 7 (Fingerprinting) for the conceptual foundations.