TL;DR
Since March 2024, Google Consent Mode v2 is required for EEA/UK traffic when you use Google Ads or GA4 with remarketing — otherwise you lose conversion modeling and part of the signals for Smart Bidding. In Next.js, implementation has three layers: default “denied” state in gtag('consent', 'default', …) before tags load, a banner storing user choice (CookieBanner.tsx + localStorage), and gtag('consent', 'update', …) after analytics/marketing categories are accepted. On DevStudio.it, GA4 (G-3HT7CZTN7P) and three Ads containers (AW-17557280025, AW-17769880693, AW-18151506857) load in layout.tsx — the banner in [locale]/layout.tsx saves preferences, but must be wired to Consent Mode so “Necessary only” actually blocks ad signals. Below: implementation, GDPR, testing, and common mistakes.
Who this is for
- Owners of business sites with Google Ads and GA4 in the EU/UK
- Next.js developers integrating CookieBanner with
gtag - People responsible for GDPR and marketing performance at the same time
- Teams seeing conversion data drop after deploying a cookie banner
Keywords (SEO)
consent mode v2 google, consent mode ga4 nextjs, cookie banner gdpr google ads, default denied consent mode, testing consent mode tag assistant
What Consent Mode v2 is
Consent Mode is Google’s mechanism that tells tags (gtag, GTM) the user’s consent status for storing and reading cookies and sending data to Google. v2 (mandatory from 2024 for Ads in the EEA) extends the model with signals:
| Parameter | Meaning |
|---|---|
analytics_storage |
Analytics cookies (GA4) |
ad_storage |
Advertising cookies |
ad_user_data |
v2 — sending user data for Google ads |
ad_personalization |
v2 — ad personalization (remarketing) |
Without v2, Google may limit remarketing, modeled conversions, and Ads account compliance in the EU — even if tags are technically “on the site”.
Consent Mode does not replace a cookie banner — it is the technical layer after user choice (or before it in default denied mode).
Architecture on a Next.js site — current state
In the DevStudio.it project:
Tags in src/app/layout.tsx
- GA4
G-3HT7CZTN7P—afterInteractivestrategy (important forgenerate_leadon fast form submit) - Google Ads — three
AW-containers withlazyOnload(LCP) - reCAPTCHA v3 — lazyOnload
GA4 config includes anonymize_ip: true and allow_ad_personalization_signals: false — a good GDPR starting point, but not a substitute for Consent Mode.
Banner in src/components/CookieBanner.tsx
Client component ('use client') rendered in src/app/[locale]/layout.tsx:
- After 2 s checks
localStorage.getItem('cookieConsent') - Accept all →
analytics: true,marketing: true - Necessary only →
analytics: false,marketing: false - Preferences in
cookiePreferencesas JSON
This is a solid UX pattern (delayed banner, three buttons, privacy policy link). Missing production piece: gtag('consent', 'update', …) synced with buttons — without it, localStorage and Google tags live separately.
Default denied vs granted — what to choose
Default denied (recommended in EEA)
Before user interaction, all signals except necessary are denied:
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
wait_for_update: 500,
});Tags may still load, but send cookieless pings — Google models some conversions statistically. This aligns with GDPR when the banner informs about cookies and offers a real choice.
Default granted (risky in the EU)
Everything “yes” until “Reject” — in most UODO/EDPB interpretations this does not meet opt-in standards. Use only outside the EEA or when legal counsel explicitly approves another legal basis.
wait_for_update
wait_for_update: 500 (ms) gives the banner time to set consent update before the first full hit — reduces a “flash” of full tracking before decision.
Step-by-step implementation in Next.js 15
Step 1: Consent default before GA4/Ads config
In layout.tsx, before gtag('config', 'G-...'):
<Script id="google-consent-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',
wait_for_update: 500,
region: ['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','IS','LI','NO','GB']
});
`}
</Script>The region array limits strict default to EEA+UK; outside that region you can set granted (Google supports regional maps in one call — see gtag docs).
Step 2: update in CookieBanner
After handleAcceptAll:
const grantAll = () => {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('consent', 'update', {
analytics_storage: 'granted',
ad_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted',
});
}
localStorage.setItem('cookieConsent', 'accepted');
localStorage.setItem('cookiePreferences', JSON.stringify({
necessary: true, analytics: true, marketing: true,
}));
setIsVisible(false);
};After handleAcceptNecessary — update denied:
window.gtag?.('consent', 'update', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
});Step 3: Restore consent after reload
In CookieBanner useEffect — if cookieConsent === 'accepted', read cookiePreferences and call consent update before showing the banner again:
useEffect(() => {
const consent = localStorage.getItem('cookieConsent');
const prefsRaw = localStorage.getItem('cookiePreferences');
if (consent && prefsRaw) {
const prefs = JSON.parse(prefsRaw);
window.gtag?.('consent', 'update', {
analytics_storage: prefs.analytics ? 'granted' : 'denied',
ad_storage: prefs.marketing ? 'granted' : 'denied',
ad_user_data: prefs.marketing ? 'granted' : 'denied',
ad_personalization: prefs.marketing ? 'granted' : 'denied',
});
return;
}
const timer = setTimeout(() => setIsVisible(true), 2000);
return () => clearTimeout(timer);
}, []);Step 4: Lazy load Ads only after marketing granted (optional, strict GDPR)
Alternative: do not load AW- scripts in layout at all — inject dynamically after marketing: true. Maximum compliance, but harder modeling and requires refactoring layout.tsx. Consent Mode with default denied + loaded tags is the compromise most implementations use with a proper banner.
GA4 G-3HT7CZTN7P — what changes after Consent Mode
With analytics_storage: 'denied':
- GA4 sends cookieless pings (consent mode modeling)
- Realtime reports may show fewer sessions until consent
generate_leadevents after form submit should arrive after granted — test the form after “Accept all”
With GA4 on afterInteractive, default denied must be beforeInteractive, otherwise the first page_view may fire with full cookies.
Keep GA4_MEASUREMENT_ID in src/lib/ga4-measurement-id.ts in sync with layout.tsx.
Google Ads AW- — three containers and conversions
The project uses three Ads tags (lazyOnload). Consent Mode applies to all — shared dataLayer and one consent update updates behavior for each gtag('config', 'AW-...').
After “Necessary only”:
conversion_event_submit_lead_formconversions may be modeled, not measured 1:1- Smart Bidding still gets aggregate signals, but with delay and lower granularity
- Tag Assistant shows consent status per hit
After “Accept all” — full _gcl_* cookies, fuller attribution.
Important: testing Ads conversions in incognito without marketing consent gives a different result than after acceptance — not a bug, it is GDPR.
GDPR — banner vs Consent Mode
| Legal requirement (simplified) | CookieBanner | Consent Mode |
|---|---|---|
| Information before cookies | Text + policy link | Not enough alone |
| Granular choice | “Customize” with categories | Map categories → gtag params |
| Marketing opt-in | “Necessary only” as viable action | default denied |
| Proof of consent | localStorage + optional server log | Google logs (supplement) |
localStorage is not a cookie — still document in privacy policy that preferences are stored locally. For audit, consider an endpoint logging decision hash + timestamp (no PII).
Banner categories:
- Necessary — always on (session, CSRF; reCAPTCHA may be “necessary” if it protects the form — consult legal)
- Analytics →
analytics_storage - Marketing →
ad_storage,ad_user_data,ad_personalization
The “Customize” button in the current UI shows categories, but toggles are not interactive — next evolution: real switches saving partial consent (analytics yes, marketing no).
Testing Consent Mode v2
Tag Assistant Companion
- Open production site in incognito.
- Without accepting cookies — send a test form.
- In Tag Assistant check Consent State — should be
deniedfor ad/analytics. - Accept all — refresh — repeat submit.
- Compare: GA4 Realtime, Ads conversions (24–48 h delay).
Chrome DevTools → Application
- Cookies — after denied, no
_ga,_gcl_au(until granted) - Local Storage —
cookieConsent,cookiePreferences
Google Ads — diagnostics
Under Goals → Settings → Consent mode, status should be “Detected” after default + update. “Not detected” = tags ignore signals or default is too late.
Pre-audit checklist
-
consent defaultbeforeInteractive, before GA4 config -
consent updateon every banner button - Restore consent from localStorage on reload
- Form + chatbot tested on both consent paths
- Privacy policy describes categories and Google as processor
- No dark patterns (pre-checked marketing)
Impact on Core Web Vitals
beforeInteractive consent script is small inline — minimal LCP impact. GA4 afterInteractive + Ads lazyOnload remains a good layout after adding Consent Mode — do not move everything to beforeInteractive “for full data”.
Modeled conversions with denied do not require loading all AW- before consent — Google explicitly designs this trade-off.
Consistency: form, chatbot, and i18n
Leads can come from the contact form (page.tsx → /api/submissions) or the chatbot (Chatbot.tsx → /api/chatbot/submit). Both fire generate_lead and conversion_event_submit_lead_form after HTTP 200. Consent Mode testing must cover both paths — otherwise marketing sees “gaps” in conversions despite a correct banner on the homepage.
Locales /pl, /en, /de share the same tag layout.tsx and the same CookieBanner in locale layout. Banner copy is currently Polish regardless of locale — the next i18n step is translated buttons and policy links (/${locale}/polityka). Consent Mode does not depend on language — granted/denied parameters are universal.
Implementation timeline (1–2 dev days)
| Day | Task | Verification |
|---|---|---|
| Day 1 AM | consent default beforeInteractive in layout |
Tag Assistant: denied before click |
| Day 1 PM | consent update in CookieBanner + localStorage restore |
Reload keeps choice |
| Day 2 AM | Test form + chatbot (denied / granted) | GA4 Realtime, no _ga when denied |
| Day 2 PM | Ads diagnostics + privacy policy update | Ads panel: Consent mode “Detected” |
After rollout, wait 48 h before judging Ads campaigns — conversion modeling stabilizes once traffic samples both consent states.
FAQ
Do I need GTM instead of gtag.js?
No. Consent Mode works with direct gtag in layout.tsx just like GTM. GTM eases tag management without code deploy — but Next.js Script + fixed IDs is a valid pattern.
Does “Necessary only” block GA4 completely?
With proper Consent Mode, GA4 still sends limited pings (modeling), but without analytics cookies. Reports will be less complete until granted. Expected behavior.
Does reCAPTCHA require marketing consent?
Google reCAPTCHA is a separate service — often classified as “necessary” for form security, but sends data to Google. Supervisory authorities may require policy mention. Consent Mode does not control reCAPTCHA iframe — consider loading script only on form focus.
What if the user clears localStorage?
Banner appears again; default denied applies until next decision. Correct behavior.
Is Consent Mode v2 enough for Google Ads in Poland?
Technically yes — with default denied, update after consent, and a GDPR-compliant banner. Legally you also need privacy policy, possibly DPA with Google, and process documentation.
Why three AW tags?
Ad account / historical campaign migration. Consent Mode updates shared dataLayer — one gtag('consent', 'update', …) is enough, not three.
Summary
Consent Mode v2 is the bridge between GDPR and effective Google Ads/GA4: default denied in the EEA, banner with real choice (like CookieBanner.tsx), gtag consent update synced with localStorage, Tag Assistant tests on both consent paths. Banner alone without Consent Mode is half an implementation; tags alone without banner is legal risk. On Next.js 15 set default beforeInteractive, keep GA4 afterInteractive for leads and Ads lazyOnload for performance — then wire banner buttons to analytics_storage and ad_ v2 parameters.
Want Consent Mode implemented on your site?
- Contact us — we will connect banner, GA4, and Google Ads in a GDPR-compliant way
- Privacy policy and cookies — documentation pattern on site
- Websites — Next.js, analytics, and legal compliance in one project