ePrivacy Art. 5(3) prohibits accessing or storing information on a device without prior consent. GDPR Art. 6(1)(a) requires a lawful basis — and for non-essential tracking, consent is the only available basis. When GA4, Facebook Pixel, or Segment loads via a static <script> tag or an unconditional strategy='afterInteractive' Next.js <Script>, it fires before any user interaction, collecting device identifiers and behavioral data without a lawful basis. CCPA §1798.135 imposes the parallel obligation for California residents' right to opt out of sale/sharing of personal information, which ad pixels facilitate. This is the most commonly litigated technical failure in DPA enforcement actions.
Critical because a static or unconditionally loaded analytics script fires on every page view, collecting personal data without any lawful basis under GDPR Art. 6(1)(a) and ePrivacy Art. 5(3) — the core violation regulators fine for.
Either use GA4 Consent Mode v2 with analytics_storage: 'denied' as the default (the only pattern that allows GA4 in the static layout), or move all other non-essential scripts into a loadApprovedScripts() function called only after the user saves consent.
// GA4 Consent Mode v2 — set the denied default BEFORE the gtag script tag
<Script id="consent-defaults" strategy="beforeInteractive">{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
});
`}</Script>
<Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX" strategy="afterInteractive" />
For scripts without Consent Mode support (Segment, Hotjar, Facebook Pixel), inject them into the DOM only inside the consent callback in src/lib/consent-scripts.ts. Never place them as static <Script> tags with strategy='afterInteractive' — that strategy is not consent-aware.
ID: cookie-consent-compliance.consent-enforcement.scripts-conditional-on-consent
Severity: critical
What to look for: This is the most technically important check in the audit. Find every non-essential script that the application loads. For each one, trace its loading path: (1) Is it a static <script> tag in the HTML <head>? If so, it always loads — this is a violation unless it is a consent-exempt essential script. (2) Is it a Next.js <Script> component? Check its strategy prop: strategy="beforeInteractive" or strategy="afterInteractive" loads without consent; strategy="lazyOnload" still loads without consent. None of these strategies are consent-aware. (3) Is it dynamically appended to the DOM after a consent callback? This is the correct pattern. (4) Is it imported as a JavaScript module (import mixpanel from 'mixpanel-browser') and initialized unconditionally at module load time? This is a violation — initialization must be inside a consent check. Check GA4 specifically: does the layout contain a gtag('consent', 'default', { analytics_storage: 'denied' }) call before the GA4 script loads? GA4 Consent Mode v2 is acceptable — it defers measurement until consent is granted — but requires correct setup.
Pass criteria: Count all third-party script tags and tracking pixels in the codebase. Every non-essential script (analytics, pixels, marketing tools, A/B testing, chat widgets) is either: (a) dynamically loaded only after the user's consent for its category is true, OR (b) using an official consent mode integration (e.g., GA4 Consent Mode v2) with analytics_storage: 'denied' as the default state. No non-essential script tag appears statically in the HTML layout without a consent gate. 100% of non-essential scripts must be conditional on user consent. Report the count of conditional vs unconditional scripts even on pass.
Fail criteria: Should not pass when any analytics or marketing script loads before the user has given consent. Analytics or tracking scripts appear as static <script> tags in the layout <head> without a consent gate. Scripts are initialized at module level without a consent check. GA4 is present but Consent Mode v2 is not configured — raw measurement fires on page load regardless of consent.
Skip (N/A) when: Application loads no non-essential third-party scripts.
Detail on fail: Specify the unconsented script(s). Example: "GA4 script tag present in app/layout.tsx head with no consent gate. Fires on every page load before any user interaction." or "Facebook Pixel fbq() initialized unconditionally in pages/_app.tsx at module level, outside any consent check." or "Segment analytics.js loads via static Script strategy='afterInteractive' — loads without checking consent state.".
Remediation: Move all non-essential script loading into consent callbacks. For GA4, use Consent Mode v2 as an alternative:
// GA4 Consent Mode v2 — initialize BEFORE the GA4 script tag
// This is the only pattern that lets you put GA4 in the layout safely
// app/layout.tsx head section:
// 1. Set default denied state (no measurement until consent)
<Script id="consent-mode-default" strategy="beforeInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
});
`}
</Script>
// 2. Then load the GA4 library (it respects the denied default)
<Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX" strategy="afterInteractive" />
// 3. In your consent save handler, update consent state:
// gtag('consent', 'update', { analytics_storage: 'granted' })
For all other scripts (Facebook Pixel, Segment, Hotjar, etc.) that do not support Consent Mode, use the dynamic loading pattern:
// lib/consent-scripts.ts
export function loadApprovedScripts(consent: ConsentState) {
if (consent.analytics) {
// Load Segment only after consent
import('@segment/analytics-next').then(({ AnalyticsBrowser }) => {
const analytics = AnalyticsBrowser.load({ writeKey: process.env.NEXT_PUBLIC_SEGMENT_KEY! })
window.__analytics = analytics
})
}
if (consent.marketing) {
// Load Facebook Pixel only after consent
const script = document.createElement('script')
script.textContent = `
!function(f,b,e,v,n,t,s){/* fbq snippet */}
fbq('init', 'YOUR_PIXEL_ID');
fbq('track', 'PageView');
`
document.head.appendChild(script)
}
}
Cross-reference: For third-party script security and CSP configuration, see the Security Headers audit.