Ominvo beta launches July 23, 2026 — only 10 spots remain Join the waitlist →
All posts
Building Ominvo

A cookie banner for the analytics I don't run

July 1, 20265 min read

Section 6 of the Privacy Policy has said the same thing since I published it: "We do not use advertising or tracking cookies. We do not use Google Analytics or any third-party analytics."

Every word of that was true. It was also a way to duck the question.

The question I ducked was: what happens when I add analytics? Because "pre-launch product with no analytics" is a moment, not a plan. Vercel Analytics is on the post-launch roadmap. So is at least one product analytics tool once I have paying customers to learn from. The day I mount the first script, Section 6 becomes a lie the moment I push it, and the site is GDPR-, ePrivacy-, and CCPA-exposed for however long it takes me to retrofit consent under whatever time pressure launch has left me with.

Day 68 builds the consent machinery so that day is a two-line change instead of a two-week scramble.

What shipped

A bottom-fixed banner with three buttons — Accept all, Reject all, Manage preferences — that appears on first visit and disappears once you've made a choice. A preferences modal with two categories: Essential (locked always-on, covers Supabase auth, Cloudflare Turnstile, Stripe checkout) and Analytics (toggle). Choice persists to localStorage under cookie_consent_v1. Reopenable from a new "Cookie Preferences" link in the footer Policies column. Section 6 of the Privacy Policy rewritten to describe all of it accurately.

Two categories, one localStorage key, one commitment: if we ever add a new category or a new processor — a marketing pixel, a session recorder, a new analytics vendor — we bump the schema version and re-prompt. A prior "accept all" doesn't carry over to categories that didn't exist when you gave it.

That's the whole system.

The category that gates nothing

The Analytics toggle stores your choice. It does not, today, control any script that mounts on the page. There are no analytics scripts on ominvo.com. The banner says "we may add optional analytics later — your choice will be remembered." The modal says "currently not active — Ominvo runs no analytics scripts today. This toggle stores your choice so it is honoured the moment we add them."

I could have shipped the banner with a fake Analytics category that pretended to control something. I could have skipped the category entirely and shipped a one-button "Got it" banner. Neither felt honest. The first pretends today looks like tomorrow. The second pretends tomorrow won't happen.

The compromise is a toggle that stores a decision I can act on later. Users who reject analytics today will have that rejection honoured the day Vercel Analytics goes live. Users who accept get the same. The machinery does what it says, even when the machinery has nothing to gate yet.

What I deliberately didn't build

No <ConsentGate> wrapper component. Nothing to gate, so a wrapper would be dead code that has to be maintained until it isn't. When Vercel Analytics ships, I'll wrap that one component. Not before.

No cross-tab consent sync. Nice-to-have, not launch-blocking.

No geo-based banner variants — one banner for everyone, GDPR-strict defaults. If it works for a Belgian visitor it works for anyone.

No CMP integration. Self-built, free, and it does exactly what I need — nothing more.

The handoff doc that lied to me

Small detail from the middle of the session, related enough to belong here: the handoff doc I was working from listed a cleanup task — delete the mail-tester test row from signup_attempts — with a SQL query that referenced a created_at column.

The query failed. That column doesn't exist. signup_attempts uses attempted_at. The handoff doc had it wrong.

Hard No #71 is "read the codebase, not the handoff doc." It's in the list because this exact class of mistake keeps happening. Yesterday's post was about checks that pass without proving anything — same shape. A summary of the truth is not the truth. The truth is what's in the file.

Why bother, pre-launch

Building consent UX for a product with no visitors sounds like premature optimization. Two answers.

One: the alternative is building it under launch pressure, when every hour of engineering time goes to real customer issues and there is no version of "let me delay this by three days to think about the GDPR schema" that survives contact with the launch checklist.

Two: pre-launch doesn't mean no visitors. It means no paying customers. Traffic exists — Google is crawling, waitlist sign-ups are coming in, and Day 65's health dashboard was silently broken for four days before I noticed. If a European visitor lands on ominvo.com today and I have no consent banner, that's a regulatory exposure regardless of my revenue.

Everything on the changelog between here and August 10 is easier to ship on top of a site that already handles the boring compliance stuff. Cookie consent is boring compliance stuff. It gets built now.

Written by

The founder of Ominvo

Building review management for single-location small businesses. Join the waitlist →