Zurück zum Blog
Technical

Remix Cookie Consent: The Complete Guide

GetCookies-TeamJanuary 2, 20258 Min. Lesezeit
RemixReactImplementationTutorial

TLDR: Add GDPR-compliant cookie consent to Remix in 2 minutes. One script tag in root.tsx. Works with every deployment target. Full Google Consent Mode v2.

Read full summary

Remix embraces web standards. Cookie consent should too. One script in root.tsx gives you full GDPR compliance. This guide covers the basics, custom hooks, and patterns for every Remix deployment target.

Summary by Claude AI

Web Standards, Applied to Consent

Remix has opinions. Web standards are good. Progressive enhancement matters. Complexity should be avoided.

Traditional consent tools ignore these principles. They want npm packages, complex configuration, and client-only solutions that break without JavaScript.

GetCookies follows Remix's philosophy: use web standards, keep it simple, let it work.

The 2-Minute Setup

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

export default function App() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
        <script
          src="https://getcookies.co/api/v1/widget/loader.js"
          data-domain-id="YOUR_DOMAIN_ID"
          async
        />
      </head>
      <body>
        <Outlet />
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  )
}

That's it. Refresh your site. Cookie banner appears.

You now have:

  • GDPR-compliant consent collection
  • Google Consent Mode v2 (all four signals)
  • Automatic script blocking
  • Third-party pixel consent

No npm install. No loader configuration. No complexity.

Environment Variables with Loaders

Use Remix loaders to pass environment variables:

// app/root.tsx
import { json } from "@remix-run/node"
import { useLoaderData } from "@remix-run/react"

export async function loader() {
  return json({
    ENV: {
      GETCOOKIES_DOMAIN_ID: process.env.GETCOOKIES_DOMAIN_ID,
    },
  })
}

export default function App() {
  const { ENV } = useLoaderData<typeof loader>()

  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
        {ENV.GETCOOKIES_DOMAIN_ID && (
          <script
            src="https://getcookies.co/api/v1/widget/loader.js"
            data-domain-id={ENV.GETCOOKIES_DOMAIN_ID}
            async
          />
        )}
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  )
}
# .env
GETCOOKIES_DOMAIN_ID=your-domain-id

A Custom Hook for Consent

Create a reusable hook for consent state:

// app/hooks/useConsent.ts
import { useEffect, useState, useCallback } from 'react'

type ConsentCategory = 'analytics' | 'marketing' | 'preferences'

export function useConsent() {
  const [consent, setConsent] = useState<Record<ConsentCategory, boolean>>({
    analytics: false,
    marketing: false,
    preferences: false,
  })

  const updateConsent = useCallback(() => {
    if (typeof window !== 'undefined' && window.GetCookies) {
      setConsent({
        analytics: window.GetCookies.hasConsent('analytics'),
        marketing: window.GetCookies.hasConsent('marketing'),
        preferences: window.GetCookies.hasConsent('preferences'),
      })
    }
  }, [])

  useEffect(() => {
    updateConsent()
    window.addEventListener('getcookies:consent', updateConsent)
    return () => window.removeEventListener('getcookies:consent', updateConsent)
  }, [updateConsent])

  const showPreferences = useCallback(() => {
    window.GetCookies?.showPreferences()
  }, [])

  const acceptAll = useCallback(() => {
    window.GetCookies?.acceptAll()
  }, [])

  const rejectAll = useCallback(() => {
    window.GetCookies?.rejectAll()
  }, [])

  return {
    ...consent,
    showPreferences,
    acceptAll,
    rejectAll,
  }
}

Use it in any route:

// app/routes/_index.tsx
import { useConsent } from '~/hooks/useConsent'

export default function Index() {
  const { analytics, marketing, showPreferences } = useConsent()

  return (
    <div>
      <h1>Welcome</h1>

      <p>Analytics: {analytics ? 'Enabled' : 'Disabled'}</p>
      <p>Marketing: {marketing ? 'Enabled' : 'Disabled'}</p>

      <button onClick={showPreferences}>
        Cookie Settings
      </button>
    </div>
  )
}

Conditional Analytics Loading

Method 1: Effect-Based Loading

// app/components/Analytics.tsx
import { useEffect } from 'react'
import { useConsent } from '~/hooks/useConsent'

export function Analytics() {
  const { analytics } = useConsent()

  useEffect(() => {
    if (analytics && typeof window !== 'undefined') {
      const script = document.createElement('script')
      script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX'
      script.async = true
      document.head.appendChild(script)

      window.dataLayer = window.dataLayer || []
      function gtag(...args: any[]) {
        window.dataLayer.push(args)
      }
      gtag('js', new Date())
      gtag('config', 'G-XXXXXXXX')
    }
  }, [analytics])

  return null
}

Add to root:

// app/root.tsx
import { Analytics } from '~/components/Analytics'

export default function App() {
  return (
    <html>
      <head>
        {/* GetCookies script */}
      </head>
      <body>
        <Outlet />
        <Analytics />
        <Scripts />
      </body>
    </html>
  )
}

Method 2: Data Attributes

Let GetCookies manage script activation:

// app/root.tsx
export default function App() {
  return (
    <html>
      <head>
        <script
          src="https://getcookies.co/api/v1/widget/loader.js"
          data-domain-id="YOUR_DOMAIN_ID"
          async
        />

        {/* Only loads after analytics consent */}
        <script
          type="text/plain"
          data-cookieconsent="analytics"
          src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"
        />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  )
}

Resource Routes for Consent Sync

Want to log consent server-side? Create a resource route:

// app/routes/api.consent.ts
import { json } from "@remix-run/node"
import type { ActionFunctionArgs } from "@remix-run/node"

export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData()
  const consentData = formData.get('consent')

  if (typeof consentData === 'string') {
    const consent = JSON.parse(consentData)

    // Log consent for audit purposes
    console.log('Consent received:', consent)

    // Store in database if needed
  }

  return json({ success: true })
}

Sync from client:

// app/hooks/useConsentSync.ts
import { useFetcher } from '@remix-run/react'
import { useEffect } from 'react'

export function useConsentSync() {
  const fetcher = useFetcher()

  useEffect(() => {
    const handleConsent = (e: CustomEvent) => {
      fetcher.submit(
        { consent: JSON.stringify(e.detail) },
        { method: 'POST', action: '/api/consent' }
      )
    }

    window.addEventListener('getcookies:consent', handleConsent as EventListener)
    return () => {
      window.removeEventListener('getcookies:consent', handleConsent as EventListener)
    }
  }, [fetcher])
}

TypeScript Definitions

// app/types/getcookies.d.ts
declare global {
  interface Window {
    GetCookies?: {
      hasConsent: (category: 'analytics' | 'marketing' | 'preferences') => boolean
      showPreferences: () => void
      acceptAll: () => void
      rejectAll: () => void
    }
    dataLayer: any[]
  }
}

export {}

Common Patterns

Footer Component

// app/components/Footer.tsx
import { useConsent } from '~/hooks/useConsent'

export function Footer() {
  const { showPreferences } = useConsent()

  return (
    <footer className="py-8 border-t">
      <nav className="flex gap-4">
        <a href="/privacy">Privacy Policy</a>
        <a href="/terms">Terms of Service</a>
        <button
          onClick={showPreferences}
          className="text-blue-600 hover:underline"
        >
          Cookie Settings
        </button>
      </nav>
    </footer>
  )
}

Consent-Gated Content

// app/components/PersonalizedSection.tsx
import { useConsent } from '~/hooks/useConsent'

export function PersonalizedSection() {
  const { marketing } = useConsent()

  if (!marketing) {
    return (
      <div className="p-4 bg-gray-100 rounded">
        <p>Enable marketing cookies to see personalized recommendations.</p>
        <button
          onClick={() => window.GetCookies?.showPreferences()}
          className="mt-2 text-blue-600"
        >
          Update Preferences
        </button>
      </div>
    )
  }

  return (
    <div>
      {/* Personalized content */}
    </div>
  )
}

Deployment Targets

GetCookies works with every Remix deployment target:

Vercel

npx vercel

Cloudflare Workers

// No changes needed - GetCookies is client-side only

Node.js

npm run build
npm start

Deno

deno task build
deno task start

Error Boundary Considerations

Include GetCookies in error boundaries for consistency:

// app/root.tsx
export function ErrorBoundary() {
  return (
    <html>
      <head>
        <title>Error</title>
        <script
          src="https://getcookies.co/api/v1/widget/loader.js"
          data-domain-id="YOUR_DOMAIN_ID"
          async
        />
      </head>
      <body>
        <h1>Something went wrong</h1>
        <Scripts />
      </body>
    </html>
  )
}

SSR Considerations

GetCookies runs client-side. Avoid hydration mismatches:

export default function ConsentAwareComponent() {
  const [mounted, setMounted] = useState(false)
  const { analytics } = useConsent()

  useEffect(() => {
    setMounted(true)
  }, [])

  // Consistent content during SSR
  if (!mounted) {
    return <GenericContent />
  }

  return analytics ? <PersonalizedContent /> : <GenericContent />
}

Performance Impact

Metric Impact
Bundle size +~1.6 KB loader (gzipped); banner ~20 KB, loaded async
LCP No impact
FID No impact
CLS No impact

Your Lighthouse scores stay excellent.

What You Don't Have to Do

  • Install any npm package
  • Configure any loader
  • Set up GTM triggers
  • Read Consent Mode documentation
  • Debug SSR timing issues
  • Wonder if it actually works

One script tag. Full compliance. Web standards, respected.


Ready to add cookie consent to your Remix app?

Get your domain ID in 60 seconds

G

GetCookies-Team

Geschrieben und gepflegt vom GetCookies-Team der Getia AS in Oslo, Norwegen. Dieser Artikel ist eine allgemeine Information zur Cookie-Einwilligung und keine Rechtsberatung.

Scannen Sie Ihre eigene Website kostenlos

Im kostenlosen Plan läuft das Banner auf einer Website, dazu drei Cookie-Scans pro Monat. Keine Kreditkarte, kein Verkaufsgespräch.