Next.jsMay 12, 2026 · 8 min

How I built a real-time, cookieless analytics console in Next.js

I replaced Google Analytics on this site with my own cookieless, first-party analytics: a tiny client collector, a Postgres pipeline behind a least-privilege role, and a real-time admin console — all in Next.js. Here's exactly how it's built and the decisions behind it.

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.

FAQ

Is cookieless analytics GDPR-friendly?

It's far easier to defend: no tracking cookies, no third-party sharing, no raw IP storage, and short retention. Always confirm specifics with counsel, but the design removes most of the usual friction.

AA
Ali Asghar

Senior software engineer & technical lead — 6+ years shipping production multi-tenant SaaS, payments and AI integration in Next.js, Node & TypeScript.

Keep reading
Multi-tenant SaaS: RLS vs schema-per-tenant vs database-per-tenantPayment webhooks the hard parts: idempotency, retries and reliability