Skip to main content

Measurement

The Consent Mode mistake that quietly costs you half your Google Ads conversions

On a lot of React and Next.js sites we review, the consent script loads in the wrong order. The reports still look fine, which is exactly what makes it expensive.

Zachary·July 8, 2026·8 min read

This is one of the most common technical problems we find in ad accounts, and it's nearly invisible unless you go looking. The reports still fill in. The conversion column still has numbers. Nothing looks broken. Meanwhile Google Ads is getting a fraction of the conversion signal it should, so its bidding algorithm is optimizing against a warped picture of what works.

If you run Google Ads and your site is built on React, Next.js, Vue, or any other client-rendered framework, there's a good chance this is happening to you.

Since March 2024, Google has required Consent Mode v2 signaling for ads shown to users in the EEA and UK. Your site has to declare, before any Google tag fires, whether the user consented to four specific things:

  • ad_storage
  • ad_user_data
  • ad_personalization
  • analytics_storage

You declare a default state, usually all denied, then send an update once the visitor answers your cookie banner. Google's tags read those signals and behave accordingly.

The word doing all the work here is before. The default declaration has to run before the tag loads. Not before the user clicks the banner. Before the tag itself initializes.

Why single-page applications break it

Here's how it breaks. In a React app, the natural home for a third-party script feels like a component. So people write a <GoogleAnalytics /> component, or a hook, or drop it in a provider. It renders, the script tag gets injected, everything looks fine.

But a React component only runs after the framework boots, builds its tree, and mounts. By then, if your tag manager loaded from a plain script tag in the head, it already initialized. It never saw the default consent state.

The expensive part comes next. The tag already committed to a consent path with no defaults. So when the visitor clicks accept and your code fires gtag('consent', 'update', {...}), that update lands on tags that already made up their minds. The signal doesn't propagate the way you'd expect.

What you see in Google Ads is a conversion count well below reality, specifically among the users who did accept your banner. People who audit this at scale report losing thirty to fifty percent of conversion signal. You're still paying for those clicks. You're just not telling Google which ones worked.

The fix

The consent default block has to be inline and synchronous, in the document head, physically above your tag manager or gtag.js snippet. Inline scripts run during HTML parsing in document order, which is the only ordering guarantee you actually get.

<script>
  window.dataLayer = window.dataLayer || [];
  function gtag() {
    window.dataLayer.push(arguments);
  }
  gtag("consent", "default", {
    ad_storage: "denied",
    ad_user_data: "denied",
    ad_personalization: "denied",
    analytics_storage: "denied",
    wait_for_update: 500,
  });
</script>
<!-- Google Tag Manager goes AFTER the block above -->

In a framework, that means the snippet belongs in whatever file produces the raw HTML shell, not in a component. For Next.js that's the root layout or _document, using beforeInteractive. For React Router in framework mode it's the Layout export in root.tsx. For a plain Vite app it's index.html.

The three details people get wrong even after fixing the order

All four signals have to be in the update, not just two. Consent Mode v1 only had ad_storage and analytics_storage, and plenty of setups built before late 2023 still send only those. Leave ad_user_data and ad_personalization out of the update and Google treats them as still denied. Basic conversion reporting keeps working, so nobody notices, while enhanced conversions quietly drop data and your remarketing audiences stop growing.

wait_for_update needs to be set. Consent platforms load asynchronously and usually take one hundred to five hundred milliseconds to initialize and read a stored preference. Without wait_for_update: 500, your tags evaluate consent before the banner has said anything at all.

Don't downgrade returning visitors. If someone accepted last week, read that stored decision inside the inline default block and set the defaults to granted right away. Otherwise every page load starts denied and waits for the update, which is slower and lossier than it needs to be.

How to verify it in ten minutes

Don't assume it works because you wrote it correctly. Check.

  1. Open your site in an incognito window with DevTools on the Network tab.
  2. Before touching the banner, find the request to google-analytics.com or googletagmanager.com and look at the gcd parameter in the query string. It encodes the consent state.
  3. Accept the banner. A new request should fire with an updated gcd value reflecting granted signals.
  4. Install Google Tag Assistant and confirm all four parameters appear on each tag fire.

If the gcd parameter never changes after you accept, the update isn't landing and you've found your problem.

Basic or advanced mode

One choice worth making on purpose. In basic mode, tags stay fully blocked until consent is granted, so you get nothing at all from people who decline. In advanced mode, tags load immediately and send cookieless pings before the banner is answered, which Google uses to model the conversions it can't observe directly.

Advanced recovers more data. It's also on shakier ground under European ePrivacy rules, because those pre-consent pings still send page URL, timestamp, and a server-observed IP address. Several EU data protection authorities have grown noticeably less comfortable with it. Basic is the safer default in 2026 unless somebody with actual legal responsibility has signed off on advanced.

Why this beats most other optimization work

We put this first on almost every project because everything downstream leans on it. Bid strategy, budget allocation, which campaigns you scale, whether a channel even looks profitable: every one of those calls comes out of conversion data. If that data is off by forty percent, and off unevenly, you're not making decisions. You're making confident guesses.

It also takes a couple of hours to fix. Almost nothing else in marketing has that ratio of effort to consequence.

Let's find out what your website could be doing

Tell us what you're trying to grow and where it's stuck right now. If we're not the right fit, we'll say so and point you somewhere better.