Why build it instead of using GA4?
Three reasons. I wanted real privacy (no third-party trackers, no cookies), I wanted data I fully control and can query directly, and — honestly — a portfolio about engineering decisions should demonstrate one. GA4 would have been faster to bolt on, but it ships visitor data to a third party and hides the raw events behind a sampling layer. Owning the pipeline was the point.
The collector: cookieless by design
The client needs to tell a returning visitor from a new one without cookies. The trick is to store a random anonymous id in localStorage (stable across sessions) and a session id in sessionStorage (resets per tab session). Neither is personal data, and there's no cross-site cookie to consent to.
function ids() {
let v = localStorage.getItem('v');
if (!v) { v = crypto.randomUUID(); localStorage.setItem('v', v); }
let s = sessionStorage.getItem('s');
if (!s) { s = crypto.randomUUID(); sessionStorage.setItem('s', s); }
return { visitorId: v, sessionId: s };
}Events are batched and flushed with navigator.sendBeacon on page hide, so leaving the page doesn't lose the last events and doesn't block navigation.
The ingest API: validate, classify, store
The /api/track route does the unglamorous but important work: same-origin check, a request rate limit, and a body-size cap so it can't be abused as a write amplifier. Geo (country/region/city) comes from edge headers — never precise GPS. The IP is never stored raw; it's run through a salted one-way hash purely to de-duplicate, and bot traffic is classified server-side and dropped from the feedback path.
Security detail: the runtime connects to Postgres through a dedicated role with only SELECT/INSERT/DELETE on two tables and no DDL. Even a total app compromise can't alter the schema.
The console: real-time without a socket
The admin dashboard polls a password-gated, paginated analytics endpoint and renders KPIs, a visitors-over-time trend, geography, and engagement. 'Real-time' here means a 5-minute active-visitor window — honest framing beats a fake live counter. The whole admin area is gated by an HMAC-signed httpOnly cookie with a 12-hour expiry, verified in timing-safe comparison.
Lessons
- Cookieless analytics is genuinely enough for product insight — you rarely need cross-site identity.
- Push validation, rate limiting and bot filtering to the edge of the pipeline, not the dashboard.
- A least-privilege database role is the cheapest blast-radius reduction you can buy.
- Be honest in the UI: 'active in last 5 min' is more trustworthy than a fake live number.
Key takeaways
- You can run useful, privacy-first analytics with no third party and no tracking cookies.
- The hard parts are ingestion hardening and data isolation, not the charts.
- Owning the pipeline means you can query and trust your own data.