[ ENGINEERING_GUIDE ][ CONSENT_MODE ][ COOKIES ][ GA4 ][ GOOGLE_ADS ]

Consent Mode v2 — cookies, GA4, and Google Ads on a Next.js site (2026)

June 10, 202610 min read
Author: DevStudio.itWeb & AI Studio

Google Consent Mode v2 step by step: cookie banner, GA4 G-3HT7CZTN7P, AW tags, default denied vs granted, GDPR, and Tag Assistant testing.

READ_TIME: 10 MIN_COMPLEXITY: MED_
STAMP: VERIFIED_BY_DS_

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

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-3HT7CZTN7PafterInteractive strategy (important for generate_lead on fast form submit)
  • Google Ads — three AW- containers with lazyOnload (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.

Client component ('use client') rendered in src/app/[locale]/layout.tsx:

  • After 2 s checks localStorage.getItem('cookieConsent')
  • Accept allanalytics: true, marketing: true
  • Necessary onlyanalytics: false, marketing: false
  • Preferences in cookiePreferences as 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

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

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 handleAcceptNecessaryupdate denied:

window.gtag?.('consent', 'update', {
  analytics_storage: 'denied',
  ad_storage: 'denied',
  ad_user_data: 'denied',
  ad_personalization: 'denied',
});

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.

With analytics_storage: 'denied':

  • GA4 sends cookieless pings (consent mode modeling)
  • Realtime reports may show fewer sessions until consent
  • generate_lead events 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.

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_form conversions 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.

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)
  • Analyticsanalytics_storage
  • Marketingad_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).

Tag Assistant Companion

  1. Open production site in incognito.
  2. Without accepting cookies — send a test form.
  3. In Tag Assistant check Consent State — should be denied for ad/analytics.
  4. Accept all — refresh — repeat submit.
  5. Compare: GA4 Realtime, Ads conversions (24–48 h delay).

Chrome DevTools → Application

  • Cookies — after denied, no _ga, _gcl_au (until granted)
  • Local StoragecookieConsent, cookiePreferences

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 default beforeInteractive, before GA4 config
  • consent update on 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 languagegranted/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.

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.

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.

About the author

We build fast websites, web/mobile apps, AI chatbots and hosting setups — with a focus on SEO and conversion.

Recommended links

From theory to production — Branchly, our hosting stack, care plans and shipped work.

LIKE HOW WE THINK? LET'S BUILD SOMETHING TOGETHER.

[ START_PROJECT_CONFIGURATION ]