GDPR doesn't forbid cookies. It forbids tracking cookies without consent. If you're running analytics (PostHog, Plausible, Google Analytics), you need to ask permission before firing pixels. If you're setting session cookies or a payment token, you're fine — those are essential, exempt from consent. The problem is most teams implement cookie consent wrong: they show a banner to every visitor (including non-EU), use a third-party platform (OneTrust, Cookiebot) that adds 2–5 seconds to page load, inject trackers before getting consent, and still ship weak audit trails. Then they get GDPR complaints, fines, or cease-and-desists from European regulators. A simple geo-based approach avoids all of this. Detect if the visitor is in the EU. If yes, show a lightweight banner before tracking. If no, skip it. Store consent in localStorage for speed, Supabase for audit. Done.
GDPR Cookie Consent at a Glance
The General Data Protection Regulation (GDPR) is Europe's privacy law. It applies to any organization processing personal data of EU residents, regardless of where your company is based. Cookies that track behavior (analytics, marketing, ads) are personal data processing. You need consent before you set them. Essential cookies (session, payment, security) don't need consent. Non-tracking cookies (language preference, theme) are a gray area — most regulators accept implied consent. But tracking cookies? You must ask. Fail and you face fines up to €20M or 4% of global revenue (whichever is higher). For a small site, that's existential.
The Aidxn pattern: show a banner only to EU visitors, ask for analytics consent, respect their choice, log consent in both localStorage (instant) and Supabase (auditable), and move on. Non-EU visitors see no banner. Analytics fire only after consent. Simple.
Why Generic Cookie Banners Are UX Disasters
Most sites use third-party cookie platforms (OneTrust, Cookiebot, iubenda, CookieYes). They promise compliance, granular consent categories, and audit logs. They deliver compliance. But the UX cost is brutal. These platforms are hosted on CDNs, load JavaScript on page entry, show a modal before your site renders, and block navigation until the user clicks a button. That's 1–3 seconds of delay, a forced interaction, and a destroyed first impression. Your bounce rate spikes. Your conversion rate drops. Regulators are happy, but your business dies slowly. The worst part: you're still paying monthly (€20–500 depending on size). And you're trusting a third party to never leak your consent logs.
The genius move: don't use a third-party platform. Geo-detect EU visitors with a header (Cloudflare), show a minimal banner if needed, store consent yourself, and ship no third-party scripts. No delay. No vendor lock-in. Full control. The implementation is 4 hours, not a subscription.
The Geo-Detection Pattern (Cloudflare)
Cloudflare adds headers to every request that tell you where the visitor is. If you're using Cloudflare Pages or Workers, read the cf-ipcountry header. If that's EU (FR, DE, IT, NL, PL, ES, etc.), the visitor is in the EU and needs a banner. Non-EU? Skip it. This is instant and costs nothing. Here's the pattern:
// In your Layout.astro, check the country header
---
const countryCode = Astro.request.headers.get('cf-ipcountry');
const isEU = ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE'].includes(countryCode);
---
{isEU && <CookieConsentBanner client:load />}
That's it. Only EU visitors see the banner. Non-EU pages load banner-free and fast.
Minimal Consent UI
The banner is the smallest friction possible. One line: "We use analytics to improve your experience. Accept?" Two buttons: "Accept" and "Reject." No collapsible menus. No "legitimate interest" weasel language. No forced scrolling through 47 granular categories. Just: analytics yes or no? Store the answer. Move on. Users respect straightforward consent. They despise deceptive design. If you bury the "Reject" button or use dark patterns to push "Accept All," regulators will fine you. Keep it honest.
// React component example
export const CookieBanner = () => {
const [agreed, setAgreed] = useState(null);
const handleConsent = (value) => {
localStorage.setItem('consent-analytics', value ? 'true' : 'false');
// Also log to Supabase (see next section)
setAgreed(value);
};
if (agreed !== null) return null; // Hide if decision made
return (
<div className="fixed bottom-4 right-4 bg-white shadow rounded p-4 max-w-sm">
<p className="text-sm mb-4">We use analytics to improve your experience.</p>
<div className="flex gap-2">
<button onClick={() => handleConsent(false)} className="text-sm">Reject</button>
<button onClick={() => handleConsent(true)} className="font-bold text-sm">Accept</button>
</div>
</div>
);
};
Store the result in localStorage as consent-analytics: 'true' or 'false'. On page load, check localStorage before firing any tracking pixels.
Consent Flow: localStorage + Supabase
localStorage gives you instant consent reads (no network). Supabase gives you an audit log (proof of compliance). Wire them together:
On Accept/Reject:
- Write to localStorage immediately (synchronous, instant).
- Fire an API call to log the decision to Supabase (asynchronous, for audit).
- After consent is stored, fire your analytics pixel (PostHog, Plausible, GA).
// POST /api/log-consent
async (request) => {
const { consent, ip, userAgent, timestamp } = await request.json();
// Insert into Supabase
const { data, error } = await supabase
.from('consent_log')
.insert({
consent_value: consent, // 'accept' or 'reject'
visitor_ip: ip,
user_agent: userAgent,
created_at: timestamp,
});
if (error) console.error('Consent log failed:', error);
return new Response(JSON.stringify({ ok: !error }));
};
Supabase table schema:
CREATE TABLE consent_log (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
consent_value TEXT NOT NULL, -- 'accept' or 'reject'
visitor_ip TEXT,
user_agent TEXT,
created_at TIMESTAMP DEFAULT now()
);
This table is your GDPR audit trail. If a regulator asks "show me proof of consent," you export this table. Timestamps, IP addresses, user agents — all documented. It's legally defensible.
Integrating with Analytics (PostHog, Plausible)
PostHog and Plausible let you control when tracking fires. Check localStorage before initialization:
// In your Layout.astro or a script tag
const consent = localStorage.getItem('consent-analytics');
if (consent === 'true') {
// Fire PostHog
window.posthog = new PostHog('phc_yourkey', { api_host: 'https://app.posthog.com' });
}
// For Plausible, it's even simpler:
// <script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script>
// Plausible respects localStorage('consent-analytics') natively if you set it up correctly
Only fire tracking pixels if consent is 'true'. If it's 'false' or unset (for non-EU), skip it. This respects consent and keeps your analytics clean.
Six FAQs
Do I need consent for essential cookies (session tokens, payment info)?
No. GDPR exempts cookies that are strictly necessary for the service to function. Session tokens, CSRF tokens, payment method storage, security cookies — all exempt. You can set these without asking. Only tracking and marketing cookies need consent. Document which cookies are essential in your privacy policy.
Is geo-detection by Cloudflare reliable enough?
Yes. Cloudflare uses MaxMind's GeoIP database (same as Google, Stripe, Amazon). It's 99%+ accurate for country-level detection. False positives (non-EU users seeing the banner) are rare. False negatives (EU users not seeing the banner) are also rare. If an EU user is on a VPN in the US, they might not see the banner — but that's their choice. Regulators accept geo-detection as good-faith compliance. It's better than a blanket global banner.
What if I use a non-Cloudflare host?
MaxMind and IP2Location sell IP geolocation databases. Download their free tier, query it on the server, and apply the same pattern. Or use an edge function (Vercel, Netlify) that reads x-vercel-ip or similar. The principle is identical: detect EU on the server, send a flag to the client, show the banner only if needed.
Can users withdraw consent later?
Yes. GDPR requires users to be able to withdraw consent as easily as they gave it. Add a "Settings" link in your footer that opens a modal: "Consent Settings — Analytics: [Toggle]." Update localStorage and log the withdrawal to Supabase. Users expect this. It's low friction and regulators love it.
Do I need a privacy policy if I'm using geo-detection and minimal consent?
Yes. You still need a clear privacy policy that explains: what cookies you use, why, how long they persist, how to withdraw consent, and how to contact you about privacy. The policy doesn't have to be 10,000 words. One paragraph per cookie type is enough. See the Privacy Act post for policy structure — the same patterns apply to GDPR.
What's the difference between GDPR and CCPA?
GDPR (EU) requires affirmative consent before tracking. CCPA (California) requires opt-out — you can track by default, but give users a "Do Not Sell My Personal Information" link. CCPA fines are lower (max $7,500 per violation) but equally annoying. If your site serves both EU and California visitors, ship the GDPR banner to EU and a simpler opt-out link to California. Geolocation handles this.
The Bottom Line
GDPR consent doesn't have to destroy your site. Geo-detect EU visitors, show a minimal banner, store consent in localStorage + Supabase, and move on. No third-party platforms. No delayed page loads. No sketchy audit trails. The implementation is straightforward, the compliance is airtight, and your users see a clean experience. If you're building a SaaS or a side project with EU traffic, ship this pattern on day one. It takes 4 hours and saves you from a regulator complaint down the line.
Cookie consent is just the first step in privacy. Check out Aidxn Design's privacy audit service for a full compliance review, or learn how to balance tracking with trust in privacy law in Australia.