Under GDPR Art. 7 and ePrivacy Art. 5(3), loading analytics or marketing cookies before a user grants consent is unlawful. Without a properly structured banner, you are collecting data that regulators can treat as illegally obtained, exposing the business to fines up to €20M or 4% of annual global turnover. Beyond fines, pre-ticked boxes or absent opt-in controls constitute consent theater — any downstream marketing use built on that data is also non-compliant under CCPA §1798.100. ISO-27001:2022 A.5.34 requires organizations to identify and fulfill applicable privacy obligations; a missing or broken consent banner is a direct gap against that control.
Critical because absent or pre-ticked consent exposes every visitor's data to unauthorized collection, and regulators treat each affected user as a separate violation.
Add a consent-gated analytics wrapper so non-essential scripts only initialize after the user accepts. Mount the banner in your root layout and store the result in localStorage before any tracker fires.
// components/CookieConsent.tsx
'use client'
import { useState, useEffect } from 'react'
type ConsentState = { analytics: boolean; marketing: boolean }
export function CookieConsent() {
const [show, setShow] = useState(false)
const [consent, setConsent] = useState<ConsentState>({ analytics: false, marketing: false })
useEffect(() => {
if (!localStorage.getItem('cookie_consent')) setShow(true)
}, [])
function save(state: ConsentState) {
localStorage.setItem('cookie_consent', JSON.stringify(state))
setShow(false)
if (state.analytics) window.gtag?.('consent', 'update', { analytics_storage: 'granted' })
}
if (!show) return null
return (
<div className="fixed bottom-0 left-0 right-0 p-4 bg-white border-t shadow-lg">
<p>We use cookies. Essential cookies are always active.</p>
<label>
<input type="checkbox" checked={consent.analytics}
onChange={e => setConsent(c => ({ ...c, analytics: e.target.checked }))} />
Analytics
</label>
<button onClick={() => save({ analytics: false, marketing: false })}>Reject all</button>
<button onClick={() => save({ analytics: true, marketing: true })}>Accept all</button>
<button onClick={() => save(consent)}>Save preferences</button>
</div>
)
}
All non-essential categories must default to unchecked. Wire the saved state to your analytics initialization before any <Script> or gtag call fires.
ID: data-protection.data-collection-consent.consent-banner-present
Severity: critical
What to look for: Enumerate every relevant item. Look for a cookie consent banner or cookie policy notice that appears on the initial page load. Search for component names like CookieBanner, ConsentBanner, CookieConsent, GDPRBanner. Check the root layout (layout.tsx, _app.tsx, App.vue, +layout.svelte) for consent component imports. Inspect the consent component itself: does it present separate toggles or checkboxes for different cookie types (essential/necessary, analytics, marketing)? Verify that non-essential categories are NOT pre-checked — GDPR opt-in requires explicit user action, not opt-out. Check whether the banner fires before any analytics or tracking scripts initialize (consent-first pattern) or whether trackers load unconditionally.
Pass criteria: At least 1 of the following conditions is met. A consent banner appears on first load for new visitors (no prior consent stored). It clearly distinguishes essential from non-essential cookie categories (at minimum: essential vs. analytics). All non-essential categories default to unchecked/off. Users have the option to accept all, reject all, or configure per category. Non-essential scripts (analytics, tracking pixels) only load after the user grants consent for that category. Before evaluating, extract and quote the relevant configuration or code patterns found. Report the count of items checked even on pass.
Fail criteria: No consent banner present on first load. The banner pre-checks non-essential categories (requiring users to actively uncheck rather than opt-in). The banner shows a single accept/decline with no category distinction. Analytics or tracking scripts load unconditionally regardless of consent state.
Do NOT pass when: The item exists only as a placeholder, stub, or TODO comment — partial implementation does not count as passing.
Skip (N/A) when: The application is a static website with no cookies, analytics, or user tracking of any kind, and no third-party scripts.
Cross-reference: For related security patterns, the Security Headers audit covers server-side hardening.
Detail on fail: Specify what is missing or incorrect. Example: "No consent banner found. Google Analytics loads unconditionally on every page." or "Consent banner present but marketing and analytics cookies are pre-ticked. User must manually uncheck to opt out." or "Banner has single Accept/Decline — no per-category granularity.".
Remediation: Implement a consent manager. Options range from managed platforms to lightweight custom implementations:
Option A — Use a managed consent platform:
# Cookiebot (auto-scans and classifies cookies)
# Add to <head> before any tracking scripts:
# <script id="Cookiebot" src="https://consent.cookiebot.com/uc.js"
# data-cbid="YOUR_ID" type="text/javascript" async></script>
# Iubenda (GDPR/CCPA/ePrivacy)
npm install iubenda-react
Option B — Lightweight open-source (Next.js example):
// components/CookieConsent.tsx
'use client'
import { useState, useEffect } from 'react'
type ConsentState = { analytics: boolean; marketing: boolean }
export function CookieConsent() {
const [show, setShow] = useState(false)
const [consent, setConsent] = useState<ConsentState>({
analytics: false,
marketing: false,
})
useEffect(() => {
const stored = localStorage.getItem('cookie_consent')
if (!stored) setShow(true)
}, [])
function save(state: ConsentState) {
localStorage.setItem('cookie_consent', JSON.stringify(state))
setShow(false)
if (state.analytics) window.gtag?.('consent', 'update', { analytics_storage: 'granted' })
}
if (!show) return null
return (
<div className="fixed bottom-0 left-0 right-0 p-4 bg-white border-t shadow-lg">
<p>We use cookies. Essential cookies are always on.</p>
<label>
<input type="checkbox" checked={consent.analytics}
onChange={e => setConsent(c => ({ ...c, analytics: e.target.checked }))} />
Analytics (helps us improve the product)
</label>
<label>
<input type="checkbox" checked={consent.marketing}
onChange={e => setConsent(c => ({ ...c, marketing: e.target.checked }))} />
Marketing
</label>
<button onClick={() => save(consent)}>Save preferences</button>
<button onClick={() => save({ analytics: true, marketing: true })}>Accept all</button>
<button onClick={() => save({ analytics: false, marketing: false })}>Reject all</button>
</div>
)
}
Load analytics conditionally after consent is granted. Use Google's Consent Mode v2 for GA4 to defer data collection until consent.