Skip to content
NewMoving 305 GitHub repos from an org to my own account
Logbook
Oct 30, 2026·4 MIN READ
Analytics showed nothing although cookies were accepted

Analytics showed nothing although cookies were accepted

GA4 on mtl.dev stayed empty after I accepted my own cookie banner. The banner stored the choice but never told gtag about it on the next page load, so every hit went out as a denied ping. One consent update on mount fixed it.

On October 29 I opened the Realtime report for mtl.dev, browsed my own site with cookies accepted, and saw zero users. The tag was there, the measurement ID was right, the cookie banner was wired to gtag. It still took two fixes before the first hit arrived, and the second one is the lesson.

The ID pointed nowhere

The first problem was dull. The measurement ID in nuxt.config.ts did not belong to any Analytics property my account could see. The GA4 property for the site existed but had no data stream at all. I added a web stream for mtl.dev, swapped the ID, deployed, and watched the network tab: the gtag script loaded with the new ID and a page_view went out to the collect endpoint. Realtime still showed nothing.

The hit said denied

The request told the story. Two parameters in the collect URL matter here:

collect request, before the fix
gcs=G100   consent: analytics denied, ads denied
status 503

G100 is consent mode's way of saying nothing was granted. My browser had cookies accepted, the banner's own checkboxes showed them accepted, and the hit still went out as a cookieless ping. Google answers those with a 503, and as far as I can tell they never reach Realtime; they only feed the modelled reports.

The site uses the nuxt-gtag module with consent mode v2. The module pushes a consent default with everything denied before gtag loads, which is correct. The banner component read the stored choice from localStorage on mount, used it to tick its checkboxes, and stopped there. The only place it called gtag('consent', 'update', ...) was the click handler on the Accept and Save buttons. So the update happened once, on the day you clicked, and every page load after that started from the denied default and stayed there.

A look at the data layer confirmed it: one consent default entry, no update.

The fix is one call on mount

The stored choice has to be replayed to gtag every time the page loads:

CookieConsent.vue, the mount hook
const { gtag } = useGtag()

onMounted(() => {
  const stored = loadConsentPreferences()
  if (stored) {
    gtag('consent', 'update', stored)
  } else {
    isOpen.value = true
  }
})

loadConsentPreferences already parsed the localStorage value; it now returns it instead of only setting the UI state. With no stored choice the banner opens as before. After the deploy the same page load sends gcs=G111 and the visit shows up.

Do not test consent by looking at the banner. Look at the collect request. The gcs parameter tells you what gtag believes, and that can differ from what your UI believes.

The default must still be pushed before gtag loads, with wait_for_update set, otherwise the first page view fires before the replay lands. The module does that for you; the replay is your job.

What it took

Two deploys and twenty minutes with the network tab, most of them spent on the wrong half of the problem. The ID swap felt like the fix because it was the visible mistake. The real one was silent: a banner that remembered the answer and never passed it on. Next time I wire a consent banner, the first test is a reload with consent already stored, not a fresh click on Accept.