Powrót do bloga
Developer

Cookie Consent for the Modern Web

Zespół GetCookiesDecember 19, 20246 min czytania
DeveloperTutorialCommunity

TLDR: Cookie consent doesn't have to tank your Lighthouse score. Here's how to add GDPR compliance in 60 seconds with a ~1.6 KB loader that sets Google Consent Mode v2 signals synchronously.

Read full summary

A developer-to-developer deep dive on cookie consent implementation. Covers why most CMPs hurt performance, how Google Consent Mode v2 works, and a solution that respects your bundle size.

Summary by Claude AI

Cookie Consent for Modern Web Developers: Why We Rebuilt It From Scratch

Originally posted on Dev.to

Last month I ran a Lighthouse audit on a client site. Performance score: 68.

The culprit? Their cookie consent banner. Not the banner itself—the 147KB JavaScript bundle it loaded.

For comparison, React is 42KB. Their consent popup was loading 3.5x the size of React. To show a popup. And ask a yes/no question.

This is the state of cookie consent in 2024. Enterprise tools built for marketing teams, deployed on developer sites, destroying Core Web Vitals everywhere they go.

Let me show you a better way.

The Real Problem: Timing

Here's what most developers don't know about Google Consent Mode.

When GTM loads, it checks consent state immediately. If your consent banner hasn't loaded yet—and it probably hasn't, because it's 100KB+ of JavaScript—GTM has no consent signals. It defaults to sending data.

This is what compliant looks like:

// MUST run before GTM
gtag('consent', 'default', {
  'ad_storage': 'denied',
  'ad_user_data': 'denied',
  'ad_personalization': 'denied',
  'analytics_storage': 'denied'
});

And this is what most CMPs actually do:

// Runs after DOMContentLoaded
// By then, GTM has already fired
gtag('consent', 'default', { ... }); // Too late

If your consent defaults don't run synchronously in the <head>, they're decorative. GTM doesn't wait.

The Technical Requirements

Here's what a proper consent implementation needs:

1. Synchronous Consent Defaults

Must run in <head> before GTM. Can't be async. Can't wait for DOMContentLoaded.

2. Google Developer ID

Google has a CMP Partner Program. Registered developers get an ID that tells Google "these consent signals are trustworthy":

gtag('set', 'developer_id.dODAxZj', true);

3. Google Consent Mode v2 Signals

Since March 2024, Google requires four specific signals:

Signal What It Controls
ad_storage Advertising cookies
ad_user_data Sending user data to Google
ad_personalization Personalized advertising
analytics_storage Analytics cookies

4. Third-Party Pixel Integration

Meta, TikTok, LinkedIn, Pinterest—they all have their own consent APIs:

// Meta
fbq('consent', 'grant');
fbq('consent', 'revoke');

// TikTok
ttq.enableCookie();
ttq.disableCookie();

// LinkedIn: Nothing. Just block the script.

5. Script Blocking

Scripts marked with type="text/plain" need to be activated after consent:

<script type="text/plain" data-cookieconsent="marketing">
  // This shouldn't run until marketing consent
</script>

What I Built

I got tired of explaining to clients why their Lighthouse score dropped after adding "just a cookie banner." So I built one that doesn't suck:

<script src="https://getcookies.co/api/v1/widget/loader.js"
        data-domain-id="your-id"
        async></script>

How It Works

~1.6 KB synchronous loader:

  • Runs immediately in <head>
  • Sets consent defaults before GTM loads
  • No blocking, no waiting

Async widget:

  • Loads the banner UI separately
  • Doesn't block rendering
  • Zero CLS (positioned fixed from start)

The API

// Check consent state
const hasAnalytics = window.GetCookies.hasConsent('analytics');
const hasMarketing = window.GetCookies.hasConsent('marketing');

// Listen for changes
window.addEventListener('getcookies:consent', (e) => {
  const { analytics, marketing, necessary } = e.detail;

  if (marketing && !window.myPixelLoaded) {
    loadMyPixel();
    window.myPixelLoaded = true;
  }
});

// Open preferences modal
window.GetCookies.showPreferences();

No callbacks. No jQuery dependencies. Just modern JavaScript.

Framework Integration

Next.js (App Router)

// app/layout.tsx
import Script from 'next/script';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <head>
        <Script
          src="https://getcookies.co/api/v1/widget/loader.js"
          data-domain-id={process.env.NEXT_PUBLIC_GETCOOKIES_ID}
          strategy="afterInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Nuxt 3

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      script: [{
        src: 'https://getcookies.co/api/v1/widget/loader.js',
        'data-domain-id': process.env.GETCOOKIES_ID,
        async: true
      }]
    }
  }
});

Astro

---
// src/layouts/Layout.astro
---
<html lang="en">
  <head>
    <script
      src="https://getcookies.co/api/v1/widget/loader.js"
      data-domain-id={import.meta.env.GETCOOKIES_ID}
      async
    ></script>
  </head>
  <body>
    <slot />
  </body>
</html>

Remix

// app/root.tsx
import { Links, Meta, Outlet, Scripts } from '@remix-run/react';

export default function App() {
  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
        <script
          src="https://getcookies.co/api/v1/widget/loader.js"
          data-domain-id={ENV.GETCOOKIES_ID}
          async
        />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  );
}

Bundle Size Comparison

I care about Core Web Vitals. You probably do too. Here's the reality:

CMP Bundle Size LCP Impact TBT Impact
OneTrust 80-150KB Severe Moderate
Cookiebot 45KB Noticeable Low
Termly 35KB Noticeable Low
CookieYes 30KB Noticeable Low
GetCookies ~1.6 KB (loader) Negligible None

The ~1.6 KB loader sets consent defaults. The banner UI loads async and doesn't block anything.

Handling Third-Party Scripts

Option 1: Data Attributes

<!-- Blocked until marketing consent -->
<script type="text/plain" data-cookieconsent="marketing">
  // Meta Pixel code
</script>

<!-- Blocked until analytics consent -->
<script type="text/plain" data-cookieconsent="analytics">
  // GA4 code
</script>

Option 2: Event Listener

window.addEventListener('getcookies:consent', (e) => {
  if (e.detail.marketing) {
    // Load Meta Pixel
    loadMetaPixel();
  }

  if (e.detail.analytics) {
    // Load GA4
    loadGA4();
  }
});

Option 3: Polling (For Already-Loaded Scripts)

// For scripts that load regardless but need consent gating
function initAnalytics() {
  if (!window.GetCookies?.hasConsent('analytics')) {
    return;
  }

  // Initialize analytics
  gtag('config', 'G-XXXXXXXX');
}

Free Tier

Because side projects shouldn't need a budget:

  • 1 domain
  • Unlimited page views
  • All core features
  • Google Consent Mode v2
  • Third-party pixel integration
  • No credit card required

The Bottom Line

Cookie consent is a solved problem. The industry just solved it with enterprise budgets and marketing team requirements.

For developers who care about performance, who want clean APIs, and who don't want to read 47 pages of Google documentation—there's now a better option.

The code:

<script src="https://getcookies.co/api/v1/widget/loader.js"
        data-domain-id="your-id"
        async></script>

The result:

  • GDPR compliant
  • Google Consent Mode v2 configured
  • Third-party pixels handled
  • Lighthouse score intact

60 seconds. ~1.6 KB. Done.


Try it: getcookies.co

Feedback welcome. What features are missing? What integrations do you need? What did I get wrong?

G

Zespół GetCookies

Napisane i aktualizowane przez zespół GetCookies w Getia AS w Oslo w Norwegii. Ten artykuł zawiera ogólne informacje o zgodzie na pliki cookie i nie stanowi porady prawnej.

Przeskanuj własną stronę za darmo

Darmowy plan wyświetla baner na jednej stronie i daje trzy skany cookies miesięcznie. Bez karty, bez rozmów handlowych.