GDPR-compliant analytics without cookies: what we built and why there's no banner
GDPR-compliant analytics without cookies is possible when you collect no PII and set no client-side identifier — which is exactly why our sites carry no cookie banner.
GDPR-compliant analytics without cookies is possible only when you collect no personal data and store no identifier on the visitor's device. Get those two things right and the legal basis for a cookie banner disappears — there is nothing to consent to. This is the route we take on every site we ship, including this one. Below is exactly what that means, what you can and can't measure, and the first-party tracker we built to stay on the right side of the line.
What "GDPR-compliant analytics without cookies" actually means
The phrase gets thrown around loosely, so let's define it. Analytics is truly cookieless and consent-free when it satisfies two independent legal tests at once:
- The ePrivacy test (the cookie law). Article 5(3) of the ePrivacy Directive — the part everyone calls "the cookie law" — governs storing or reading information on the user's device. It is not limited to cookies. It covers localStorage, IndexedDB, device fingerprinting, and any equivalent. If you neither write nor read anything on the device that isn't strictly necessary to deliver the page the user asked for, you are outside Article 5(3) and you do not need consent.
- The GDPR test (the personal-data law). The GDPR governs processing personal data — anything that can identify a person directly or indirectly, including an IP address (the CJEU settled this in Breyer, C-582/14). If you process no personal data, the GDPR's consent and lawful-basis machinery largely falls away.
Pass both and you've earned the thing every marketing team secretly wants: no banner, no consent flow, and analytics that still works for 100% of visitors instead of the ~60% who click "accept". Most "cookieless" tools on the market pass the first test and quietly fail the second, because they still hash an IP and a user agent into a daily visitor ID. That is a device-derived identifier and, under Breyer reasoning, very likely personal data.
The short version
- No cookies, no localStorage, no fingerprint. We write nothing to the device, so Article 5(3) doesn't apply.
- No PII, no IP storage, no cross-site ID. We process no personal data, so the GDPR consent question never arises.
- One anonymous beacon per page view, stored first-party on our own server. No third party ever sees the visitor.
- Result: no cookie banner, and counts that cover every visitor, not a consented subset.
What you can measure without cookies (and what you can't)
Honesty matters more than the pitch here, because the tradeoff is real. Cookieless, identifier-free analytics is aggregate-only. You are counting events, not following people.
What you can answer cleanly:
- How many page views did
/workget yesterday, this week, this month. - Which paths are most popular, and the rough shape of traffic over time.
- Where visits originate at the origin level —
google.com,linkedin.com, direct — not the full referring URL with its query string. - Coarse device context: viewport width bucket, preferred language. Enough to know whether mobile layout matters.
What you cannot answer, and shouldn't pretend to:
- Unique visitors and returning visitors. There is no stable ID, so "uniques" is not a number you can produce honestly. Anyone selling you cookieless "unique visitors" is reconstructing an identifier somewhere.
- Multi-step funnels tied to one person. You can count how many sessions hit step 1 and step 2; you cannot prove it was the same human walking the path.
- Cross-device or cross-site journeys. Gone by design. That is the whole point.
For a studio site, a marketing site, a portfolio, or most content businesses, the aggregate view is enough to make decisions. If your business model genuinely needs per-user attribution — a high-velocity e-commerce funnel, say — then you need consent, and you should ask for it honestly with a real banner rather than dress up tracking as "privacy-first". We told Delicious Diamonds exactly that for their members-only commerce flows: the public marketing site runs cookieless, the authenticated purchase journey uses first-party session data the customer has already agreed to by logging in.
How we built it: one beacon, no identity
Our stack is deliberately boring — PHP 8.3 on shared hosting, Cloudflare in front, no build step, server-rendered. The tracker fits that philosophy. There is no third-party script, no SDK, no npm dependency. It's a few lines of vanilla JavaScript and one PHP endpoint.
The client fires a single POST roughly three seconds after load (long enough to skip most bounces and bots), then forgets it ever happened:
// no cookies, no localStorage, no identity
navigator.sendBeacon('/api/hit', JSON.stringify({
path: location.pathname,
referrer_origin: document.referrer
? new URL(document.referrer).origin
: null,
lang: navigator.language.slice(0, 2),
screen_w: window.innerWidth,
ts: Date.now()
}));
Read that payload carefully, because the privacy guarantee lives in what's absent. There is no user agent. No IP — and critically, the server is configured never to log or persist the connecting IP for these requests. No UUID, no hash of anything, no session token. referrer_origin deliberately strips the path and query string off the referrer, so we learn you came from LinkedIn but not which post or which UTM trail. The language is truncated to two characters. None of these fields, alone or combined, singles out a person.
On the server, the endpoint appends one line of JSON to a monthly file:
// /api/hit -- append-only, no IP, no identity
$line = json_encode([
'path' => $clean['path'],
'ref' => $clean['referrer_origin'],
'lang' => $clean['lang'],
'w' => (int) $clean['screen_w'],
't' => time(),
]) . "\n";
file_put_contents(
__DIR__ . '/../storage/hits/' . date('Y-m') . '.jsonl',
$line,
FILE_APPEND | LOCK_EX
);
The storage/hits/ directory is locked down at the web-server level with an .htaccess carrying Require all denied, so the raw logs are never reachable over HTTP. A small PHP dashboard behind HTTP basic auth reads those JSON-lines files, aggregates by day, and renders six numbers and a bar chart. The whole thing runs on-server; no data leaves our infrastructure, and no third party — no Google, no analytics vendor — ever touches the visitor.
Why that means no cookie banner
Walk it back through the two tests.
Article 5(3): we store nothing on the device and read nothing back. sendBeacon is a fire-and-forget HTTP request; it sets no cookie and touches no storage. There is no "access to information stored in the terminal equipment", so the consent requirement of the cookie law is not triggered. Nothing to ask about.
GDPR: we process no personal data. No IP is stored, no identifier is derived, and none of the retained fields — a path, a referrer origin, a two-letter language, a viewport width, a timestamp — identifies a natural person, individually or in combination. With no personal data in scope, there is no Article 6 lawful basis to establish and no consent to collect.
Two tests passed, zero banners required. The legal conclusion is not a clever loophole; it's the direct consequence of having genuinely nothing to declare. The banner exists to obtain consent for storage and for processing personal data. Do neither and the banner has no job to do.
FAQ
Is Google Analytics ever GDPR-compliant without a banner?
No. GA4 sets cookies and processes identifiers and IP-derived data, which engages both the ePrivacy and GDPR tests. It requires consent in the EU, and several DPAs (Austria, France, Italy) have ruled standard GA configurations unlawful over US data transfers. If you run GA, you need a banner and a consent flow.
What about "cookieless" tools like Plausible or Fathom?
They're a big improvement and avoid cookies, but most generate a daily visitor hash from IP plus user agent to estimate uniques. That hash is a device-derived identifier and, following Breyer, is arguably personal data. They're privacy-respecting and often defensible without a banner, but it is a finer legal line than "we store and derive nothing", which is the line we chose to stand on.
Don't you lose important data?
You lose per-user data. You keep every aggregate number that actually informs a content or design decision, and you keep it for all visitors instead of the minority who accept a banner — which often makes the aggregate picture more accurate, not less.
Does Cloudflare break the no-PII promise?
Cloudflare sits in front as a CDN and sees IPs at the network layer, as any host does. The distinction that matters legally is that we don't log, store, or process those IPs in our analytics; the beacon payload contains no IP and the endpoint never records the connecting address. Network-level transit is not the same as collecting personal data into an analytics dataset.
If you want this
The pattern is portable. Any server-rendered stack can ship it: a single beacon, a strip-everything payload, an append-only first-party store, no third party, no identifier. The hard part isn't the code — it's the discipline to not collect the data that makes a banner mandatory. We build sites this way by default because it's faster, lighter, and it means the first thing a visitor sees is the work, not a consent dialog. If you want analytics you can defend without a privacy team and a legal review, this is the shape of it.