Zum Inhalt springen
    WingoUI
    KomponentenVorlagenDokuPreiseKI-AgentenBlogChangelog
    1. Startseite
    2. Blog
    3. Next.js und Tailwind CSS
    4. Next.js Cookie Banner: DSGVO und Consent Mode v2
    Read in English
    1. Blog
    2. Next.js und Tailwind CSS
    3. Next.js Cookie Banner: DSGVO und Consent Mode v2
    Next.js und Tailwind CSS
    Next.js und Tailwind CSS

    Next.js Cookie Banner: DSGVO und Consent Mode v2

    SR
    Serban Rusu · Gründer von Wingo UI
    16. Okt. 2026 · 12 Min. Lesezeit

    Auf dieser Seite

    0%
    1. Was verlangt die DSGVO wirklich von einem Cookie-Banner?
    2. Wie baust du einen Cookie-Banner in Next.js ein?
    3. Wie baust du einen React Cookie Banner mit Schaltern pro Kategorie?
    4. Wie richtest du Google Consent Mode v2 in Next.js ein?
      1. Schritt 1: die Standardwerte vor allem anderen setzen
      2. Schritt 2: das Update senden und den Tag aus der gespeicherten Wahl laden
    5. Wie blockierst du YouTube-Embeds bis zur Einwilligung?
    6. Wie sieht der gespeicherte Consent-Datensatz aus?
    7. Was solltest du als Nächstes tun?
    8. Häufige Fragen
      1. Braucht ein Cookie-Banner einen Button „Alle ablehnen“?
      2. Wie richte ich Google Consent Mode v2 in Next.js ein?
      3. Kann ich die Cookie-Einwilligung in localStorage statt in einem Cookie speichern?
      4. Warum blitzt mein Cookie-Banner bei Besuchern auf, die schon gewählt haben?
      5. Brauchen unbedingt notwendige Cookies eine Einwilligung?

    Kurz gesagt

    Ein Next.js Cookie Banner, der der DSGVO standhält, zeigt „Alle akzeptieren“ und „Alle ablehnen“ als gleichwertige Buttons auf der ersten Ebene, lässt jede optionale Kategorie aus, bis der Besucher sie einschaltet, und speichert die Wahl mit Policy-Version und Zeitstempel. Setz alle Signale von Google Consent Mode in einem beforeInteractive-Script im Root-Layout auf denied und schick dann aus einer Client-Komponente gtag('consent', 'update', ...), sobald die Wahl feststeht. Embeds von Drittanbietern wie YouTube bleiben hinter einem Platzhalter, bis ihre Kategorie erlaubt ist.

    Veröffentlicht am 16. Okt. 2026

    Bevor Analytics in der EU live gehen darf, brauchst du einen Cookie-Banner. Die schnelle Variante, ein großer Zustimmen-Button neben einem kleinen Link „Optionen verwalten“, ist genau die Form, die europäische Aufsichtsbehörden beanstandet haben. Dieses Tutorial baut einen Next.js Cookie Banner nach ihren Vorgaben: „Alle akzeptieren“ und „Alle ablehnen“ mit gleichem Gewicht, ein Einstellungs-Panel mit einem Schalter pro Kategorie, Werte für Google Consent Mode v2 aus der gespeicherten Wahl und YouTube-Embeds, die blockiert bleiben, bis Marketing-Cookies erlaubt sind.

    Der Code ist für den App Router in Next.js 16, React 19 und Tailwind CSS v4 geschrieben. Er nutzt die Cookie-Consent-Komponente für die UI und den Hook useConsent für den Datensatz dahinter. Nichts davon ist Rechtsberatung: Lies die Leitlinien deiner eigenen Datenschutzbehörde oder frag eine Anwältin oder einen Anwalt, welche Details für deine Seite gelten.

    Was verlangt die DSGVO wirklich von einem Cookie-Banner?

    Ein Banner muss die Einwilligung einholen, bevor irgendein nicht notwendiger Cookie gesetzt wird, das Ablehnen so leicht machen wie das Zustimmen und es dem Besucher erlauben, seine Entscheidung später zu ändern. Hier treffen zwei Gesetze aufeinander. Die ePrivacy-Richtlinie (Artikel 5 Absatz 3) sagt, dass du eine Einwilligung brauchst, bevor du etwas auf dem Gerät des Besuchers speicherst oder ausliest, außer es ist für den angeforderten Dienst unbedingt notwendig. Die DSGVO legt fest, was eine gültige Einwilligung ist: freiwillig, für den bestimmten Fall, in informierter Weise, durch eine eindeutige bestätigende Handlung und so leicht zu widerrufen, wie sie erteilt wurde.

    Die praktischste Lesart dieser Regeln für UI-Entwickler ist der Bericht der EDPB Cookie Banner Taskforce (opens in a new tab). Er wurde am 17. Januar 2023 angenommen, um abzustimmen, wie die Behörden mit den Beschwerden über Cookie-Banner umgehen, die noyb eingereicht hat. Er listet die Muster auf, bei denen sich die Behörden einig waren:

    • Kein Ablehnen-Button neben Zustimmen. Die allermeisten Behörden sahen einen Verstoß, wenn ein Banner auf keiner Ebene mit Zustimmen-Button eine Möglichkeit zum Ablehnen bietet.
    • Vorab angehakte Kästchen. Kategorien, die in der Einstellungsebene schon eingeschaltet sind, ergeben keine gültige Einwilligung.
    • Ablehnen als versteckter Link. Ein Link „Weiter ohne Zustimmung“, der im Fließtext untergeht und optisch kaum hervorgehoben ist, zählt nicht als echte Wahl.
    • Irreführender Kontrast. Ein Ablehnen-Button, dessen Text so kontrastarm ist, dass man ihn nicht lesen kann, kann offenkundig irreführend sein.
    • „Notwendige“ Cookies, die es nicht sind. Manche Seiten nennen Cookies „unbedingt notwendig“, die anderen Zwecken dienen. Der Betreiber muss die Cookie-Liste pflegen und zeigen können, warum jeder einzelne notwendig ist.
    • Kein Weg zurück. Besucher brauchen eine einfache, jederzeit erreichbare Möglichkeit zum Widerruf, etwa einen Link an einer üblichen Stelle oder ein kleines schwebendes Icon.

    Als Spezifikation für einen DSGVO Cookie Banner ergibt das sechs Anforderungen: „Alle akzeptieren“ und „Alle ablehnen“ nebeneinander in gleicher Größe, jede optionale Kategorie standardmäßig aus, eine Einstellungsebene mit Schaltern pro Kategorie, kein Laden optionaler Inhalte vor der Wahl, ein Link im Footer, der die Einstellungen wieder öffnet, und ein Datensatz, was wann gewählt wurde.

    Wie baust du einen Cookie-Banner in Next.js ein?

    Installier die Cookie-Consent-Komponente und render sie einmal im Root-Layout, neben einem Footer-Link, der die Einstellungen wieder öffnet. Die CLI holt auch den Hook useConsent und die Teile, auf denen der Banner aufbaut, etwa Button, Switch, Dialog und Collapsible:

    bash
    npx wingo-ui@latest add cookie-consent

    Dann kommt er ins Root-Layout, hinter den Seiteninhalt:

    tsx
    // app/layout.tsx
    import type { ReactNode } from "react"
    import { CookieConsent, CookieSettingsLink } from "@/components/ui/cookie-consent"
    import "./globals.css"
    export default function RootLayout({ children }: { children: ReactNode }) {
    return (
    <html lang="en">
    <body>
    {children}
    <footer className="flex justify-center py-8 pb-[calc(2rem+var(--cookie-consent-offset,0px))]">
    <CookieSettingsLink />
    </footer>
    <CookieConsent policyHref="/cookie-policy" delay={500} />
    </body>
    </html>
    )
    }

    Damit ist dein Next.js Cookie Consent schon komplett. Das macht der Banner ohne weitere Props:

    • Er erscheint nur bei Besuchern, die noch nicht entschieden haben. Der Datensatz liegt 180 Tage lang (maxAgeDays) in einem Cookie ui-consent, und die Leiste rendert erst nach der Hydration. Wer schon gewählt hat, sieht sie also nie aufblitzen.
    • Accept all, Reject all und Settings sind gleichwertige Buttons. tone färbt nur den Accept-Button; Reject behält einen neutralen Soft-Look in derselben Größe. Auf dem Smartphone stapeln sie sich als drei 48px hohe Buttons über die volle Breite.
    • Die Leiste ist am unteren Rand fixiert und verschiebt das Layout nie. Sie schreibt ihre sichtbare Höhe in --cookie-consent-offset auf <html>. So bleibt der Link im Footer darüber erreichbar, solange die Leiste offen ist.
    • Settings öffnet auf dem Desktop einen Dialog und unter 768px ein Bottom Sheet in voller Höhe. „Strictly necessary“ ist fest eingeschaltet; die anderen Schalter starten aus.
    • Jede Wahl wird Screenreadern über eine höfliche Live-Region (polite) angesagt.

    CookieSettingsLink ruft openConsentSettings() auf, die Einstellungen öffnen sich also aus dem Footer jeder Seite. Damit ist die Anforderung „leicht zu widerrufen“ mit einem Link an einer vorhersehbaren Stelle erfüllt.

    Drück Settings, schalte Analytics ein und Marketing aus und drück dann Save choices: Die Leiste gleitet weg, und der Footer-Link holt die Einstellungen zurück
    $ npx wingo-ui@latest add cookie-consent
    ProDoku: Cookie Consent

    Das Root-Layout ist eine Server-Komponente, deshalb kannst du dort kein onDecide übergeben: Funktionen können die Grenze zwischen Server und Client nicht überqueren. Der nächste Abschnitt liest die Wahl stattdessen in einer kleinen Client-Komponente.

    Wie baust du einen React Cookie Banner mit Schaltern pro Kategorie?

    Übergib eine Liste categories, die zu dem passt, was deine Seite wirklich setzt: Das Einstellungs-Panel rendert pro Eintrag einen Schalter und einen Toggle „Show cookies“, der Name, Anbieter, Laufzeit und Zweck jedes Cookies auflistet. Die Standardliste ist Strictly necessary, Preferences, Analytics und Marketing. Leg deine eigene Liste in eine einfache Datendatei:

    tsx
    // app/consent-config.ts
    import type { ConsentCategoryConfig } from "@/components/ui/cookie-consent"
    export const CONSENT_VERSION = 1
    // reine daten: server-dateien importieren das, also keine werte aus einem "use client"-modul lesen
    export const CONSENT_CATEGORIES: ConsentCategoryConfig[] = [
    {
    id: "necessary",
    label: "Unbedingt notwendig",
    description: "Anmeldung, Sicherheit und das Speichern dieser Wahl.",
    required: true,
    cookies: [{ name: "ui-consent", provider: "acme.com", duration: "180 Tage", purpose: "Speichert deine Cookie-Wahl" }],
    },
    { id: "preferences", label: "Präferenzen", description: "Sprache, Theme und gespeicherte Filter." },
    {
    id: "analytics",
    label: "Statistik",
    description: "Zeigt uns, wie die Seite genutzt wird.",
    cookies: [{ name: "_ga", provider: "Google Analytics", duration: "2 Jahre", purpose: "Unterscheidet Besucher" }],
    },
    {
    id: "marketing",
    label: "Marketing",
    description: "Werbung und eingebettete Inhalte wie YouTube-Videos.",
    cookies: [{ name: "VISITOR_INFO1_LIVE", provider: "YouTube", duration: "6 Monate", purpose: "Videowiedergabe" }],
    },
    ]

    Trag die Cookies ein, die dein eigenes Audit findet; die oben sind Beispiele. Schreib das Array aus, statt über DEFAULT_CONSENT_CATEGORY_CONFIG zu mappen: Das Layout ist eine Server-Komponente, und in einer Server-Datei ist jeder Export eines "use client"-Moduls eine Client-Referenz. Eine Referenz darfst du als Prop weiterreichen, aber DEFAULT_CONSENT_CATEGORY_CONFIG.map(...) wirft beim Rendern des Layouts den Fehler You cannot dot into a client module from a server component. Das import type oben wird beim Build entfernt, ist also unproblematisch.

    Dann gibst du Liste und Version an den Banner:

    tsx
    <CookieConsent
    categories={CONSENT_CATEGORIES}
    version={CONSENT_VERSION}
    policyHref="/cookie-policy"
    delay={500}
    />

    version ist die Version deiner Policy. Wenn du eine Kategorie oder einen neuen Anbieter ergänzt, erhöh sie: Datensätze unter einer anderen Version gelten als unentschieden, die Leiste kommt also zurück und fragt erneut. Nutz überall, wo du die Einwilligung liest, dieselbe Konstante, denn useConsent, ConsentGate und der Banner nehmen jeweils version und stimmen nur überein, wenn sie gleich ist.

    Ein paar Props, die du für einen React Cookie Banner kennen solltest:

    PropStandardWas es ändert
    variant"bar""card" ist eine 400px breite schwebende Karte statt einer Leiste über die volle Breite
    showRejecttrueReject all auf der ersten Ebene; in der EU eingeschaltet lassen
    blockingfalseDunkelt die Seite ab und hält den Fokus bis zur Wahl fest; nur nach rechtlicher Beratung nutzen
    storage"cookie""local" oder "session" statt eines Cookies
    labelsenglische TexteJeder String; COOKIE_CONSENT_LABELS_RO sind fertige rumänische Texte

    Die vollständige Liste, mit dem Playground für jedes Prop, steht auf der Doku-Seite von Cookie Consent. Die Schalter im Einstellungs-Panel sind derselbe Switch, den der Rest der Bibliothek nutzt, und das Panel ist ein Dialog, der auf Smartphones zum Sheet wird.

    Wie richtest du Google Consent Mode v2 in Next.js ein?

    Setz jedes Consent-Signal in einem Inline-Script, das vor jedem Google-Tag läuft, auf denied und schick danach ein Update aus der gespeicherten Wahl. Googles Entwickler-Guide zum Consent Mode (opens in a new tab) (zuletzt aktualisiert am 13. Oktober 2026, geprüft am 16. Oktober 2026) nennt ad_storage und analytics_storage sowie die beiden Parameter, die Consent Mode v2 neu eingeführt hat: ad_user_data und ad_personalization. Der Aufruf gtag('consent', 'default', ...) muss vor jedem Befehl kommen, der Messdaten sendet, etwa config oder event. Der Guide warnt, dass die Standardwerte nicht greifen, wenn der Consent-Code in der falschen Reihenfolge läuft.

    Googles Überblick zum Consent Mode (opens in a new tab) (zuletzt aktualisiert am 30. Juli 2026, geprüft am 16. Oktober 2026) beschreibt zwei Varianten. Im Basic Consent Mode laden Google-Tags erst, wenn der Besucher mit dem Banner interagiert, und bleiben ohne Einwilligung blockiert. Im Advanced Consent Mode laden die Tags beim Öffnen der Seite und senden cookielose Messungen, solange die Einwilligung verweigert ist. Die Basic-Variante ist die strengere Lesart von „nichts Optionales vor der Wahl“, deshalb nutzt das Setup für Google Consent Mode v2 in Next.js unten genau diese.

    Schritt 1: die Standardwerte vor allem anderen setzen

    Ein Inline-next/script mit strategy="beforeInteractive" landet im initialen HTML, und der Bootstrap von Next.js führt es vor der Hydration aus. Es läuft also immer vor jeder Client-Komponente. Die Next.js-Doku verlangt, dass diese Strategie im Root-Layout steht, und ein Inline-Script braucht eine id. Weil es als klassisches Script läuft, wird function gtag() zu einer globalen Funktion, die window.gtag später aufrufen kann:

    tsx
    // app/consent-defaults.ts
    export const CONSENT_DEFAULTS = `
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('consent', 'default', {
    ad_storage: 'denied',
    ad_user_data: 'denied',
    ad_personalization: 'denied',
    analytics_storage: 'denied',
    functionality_storage: 'denied',
    personalization_storage: 'denied',
    security_storage: 'granted'
    });`

    Das Script kommt in den Body des Root-Layouts. Schritt 2 unten füllt GoogleTag; das hier ist das fertige Layout:

    tsx
    // app/layout.tsx
    import type { ReactNode } from "react"
    import Script from "next/script"
    import { CookieConsent, CookieSettingsLink } from "@/components/ui/cookie-consent"
    import { CONSENT_CATEGORIES, CONSENT_VERSION } from "./consent-config"
    import { CONSENT_DEFAULTS } from "./consent-defaults"
    import { GoogleTag } from "./google-tag"
    import "./globals.css"
    export default function RootLayout({ children }: { children: ReactNode }) {
    return (
    <html lang="en">
    <body>
    <Script id="consent-defaults" strategy="beforeInteractive">
    {CONSENT_DEFAULTS}
    </Script>
    {children}
    <footer className="flex justify-center py-8 pb-[calc(2rem+var(--cookie-consent-offset,0px))]">
    <CookieSettingsLink />
    </footer>
    <CookieConsent categories={CONSENT_CATEGORIES} version={CONSENT_VERSION} policyHref="/cookie-policy" delay={500} />
    <GoogleTag />
    </body>
    </html>
    )
    }

    Schritt 2: das Update senden und den Tag aus der gespeicherten Wahl laden

    toGoogleConsentMode(choices) aus dem Hook übersetzt die Kategorien in Consent-Mode-Werte: Analytics in analytics_storage, Marketing in die drei Werbe-Signale, Preferences in Functionality und Personalization, und security_storage ist immer granted. Eine kleine Client-Komponente schickt das Update bei jeder Änderung der Wahl und lädt gtag.js erst, wenn Analytics oder Marketing erlaubt ist:

    tsx
    // app/google-tag.tsx
    "use client"
    import { useEffect } from "react"
    import { toGoogleConsentMode, useConsent } from "@/hooks/use-consent"
    import { CONSENT_VERSION } from "./consent-config"
    declare global {
    interface Window {
    dataLayer: unknown[]
    gtag: (...args: unknown[]) => void
    }
    }
    const GA_ID = process.env.NEXT_PUBLIC_GA_ID
    let loaded = false
    export function GoogleTag() {
    const { hydrated, choices } = useConsent({ version: CONSENT_VERSION })
    useEffect(() => {
    if (!hydrated || !GA_ID || typeof window.gtag !== "function") return
    // die queue hält die reihenfolge: default, dann update, dann js und config
    window.gtag("consent", "update", toGoogleConsentMode(choices))
    if (loaded || !(choices.analytics || choices.marketing)) return
    loaded = true
    window.gtag("js", new Date())
    window.gtag("config", GA_ID)
    const script = document.createElement("script")
    script.async = true
    script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}`
    document.head.appendChild(script)
    }, [hydrated, choices])
    return null
    }

    Mit <GoogleTag /> im Layout neben dem Banner steht die Reihenfolge fest. Weil die Befehle durch die Queue dataLayer laufen, verarbeitet gtag.js sie beim Laden der Reihe nach: verweigerte Standardwerte, die echten Werte des Besuchers, dann die Config. Wer wiederkommt und zugestimmt hat, bekommt das Update beim ersten Render nach der Hydration, ohne Banner.

    choices ist aus dem gespeicherten Datensatz memoisiert, der Effect läuft also einmal beim Laden und erneut nach jeder Entscheidung. Widerruft jemand, schickt derselbe Effect denied, und der Tag hört auf, seine Cookies zu nutzen. Cookies, die er schon gesetzt hat, bleiben bis zum Ablauf im Browser. Wenn deine Policy verspricht, sie zu löschen, entfern sie in einem Listener onConsentChange aus dem Hook.

    Entscheidest du dich stattdessen für den Advanced Consent Mode, lädst du gtag.js ohne Bedingung und ergänzt wait_for_update: 500 in den Standardwerten. Googles Guide nutzt diesen Parameter, um einem asynchron geladenen Banner eine festgelegte Zahl Millisekunden Zeit für sein Update zu geben, bevor Tags Daten senden.

    Wie blockierst du YouTube-Embeds bis zur Einwilligung?

    Pack das iframe in ConsentGate, dann steht es erst im HTML, wenn Marketing erlaubt ist. Ein eingebetteter Player ist eine Seite eines Drittanbieters, die ihren eigenen Speicher lesen und beschreiben kann, sobald sie lädt. Artikel 5 Absatz 3 erfasst jeden Zugriff auf den Speicher, nicht nur Cookies. Behandle das Embed deshalb auch auf youtube-nocookie.com als Marketing, außer dein eigenes Audit sagt etwas anderes. ConsentGate rendert seine Children nur, wenn die Kategorie eingeschaltet ist, rendert während der Hydration nichts und zeigt sonst einen Platzhalter mit einem Button „Allow and load“, der genau diese eine Kategorie einschaltet:

    tsx
    // app/page.tsx
    import { ConsentGate } from "@/components/ui/cookie-consent"
    import { CONSENT_CATEGORIES, CONSENT_VERSION } from "./consent-config"
    export default function Page() {
    return (
    <ConsentGate category="marketing" categories={CONSENT_CATEGORIES} version={CONSENT_VERSION} className="aspect-video">
    <iframe
    title="Acme Produkttour"
    src="https://www.youtube-nocookie.com/embed/abc123"
    className="aspect-video w-full rounded-2xl"
    allowFullScreen
    />
    </ConsentGate>
    )
    }

    Die Seite bleibt eine Server-Komponente; nur das Gate ist eine Client-Komponente, und das iframe wird als Children durchgereicht. Der Platzhalter nennt die Kategorie aus deiner Config („This content uses marketing cookies“) und erklärt, dass der Besucher das später in den Cookie-Einstellungen ändern kann. Übergib fallback für deinen eigenen Platzhalter, etwa ein Vorschaubild mit Play-Button.

    Für deine eigenen Komponenten liefert der Hook dieselbe Prüfung. has("marketing") ist erst nach einer Entscheidung true, die es erlaubt hat, und save({ ...choices, marketing: true }) schaltet eine Kategorie ein, ohne die anderen anzufassen.

    Leg die Schalter um und drück Save choices: Die Consent-Mode-Werte darunter ändern sich, und das Video lädt erst, wenn Marketing an ist
    $ npx wingo-ui@latest add use-consent
    ProDoku: useConsent

    Wie sieht der gespeicherte Consent-Datensatz aus?

    Der Datensatz ist ein kleines JSON-Objekt mit Policy-Version, Auswahl, einem ISO-Zeitstempel und der Methode. Genau das brauchst du, um nachzuweisen, dass eine Einwilligung erteilt wurde (Art. 7 Abs. 1 DSGVO). Der Hook speichert einen Datensatz pro Website, lesbar als record aus useConsent oder mit getConsent() außerhalb von React:

    json
    {
    "version": 1,
    "choices": { "necessary": true, "preferences": false, "analytics": true, "marketing": false },
    "decidedAt": "2026-10-16T09:41:12.000Z",
    "method": "custom"
    }

    method ist accept-all, reject-all oder custom. Wenn du ein Protokoll auf dem Server brauchst, schick den Datensatz in einem Listener onConsentChange an deine API; der Cookie selbst belegt nur, was dieser Browser gespeichert hat.

    Weil der Standardspeicher ein Cookie ist, kann Server-Code denselben Datensatz lesen. Next.js dekodiert Cookie-Werte bereits, JSON.parse reicht also. Vergleich die Version selbst, so wie es der Hook tut:

    ts
    // app/server-consent.ts
    import { cookies } from "next/headers"
    import type { ConsentRecord } from "@/hooks/use-consent"
    import { CONSENT_VERSION } from "./consent-config"
    export async function readConsent(): Promise<ConsentRecord | null> {
    const raw = (await cookies()).get("ui-consent")?.value
    if (!raw) return null
    try {
    const record = JSON.parse(raw) as ConsentRecord
    return record.version === CONSENT_VERSION ? record : null
    } catch {
    return null
    }
    }

    Ein Aufruf von cookies() macht die Route dynamisch. Nutz das also in einem Route Handler oder auf einer Seite, die ohnehin dynamisch ist, nicht im Root-Layout einer statischen Seite.

    Stolperfallen, die du vor dem Release prüfen solltest:

    • Den Banner auf dem Server rendern. Eine statische Seite kennt die Wahl nicht, also blitzt ein serverseitig gerenderter Banner bei allen auf, die schon entschieden haben. Knüpf ihn an hydrated; die Komponente tut das. Der Beitrag zum Hydration-Fehler von next-themes erklärt denselben Mismatch für Themes.
    • Analytics „erst mal“ im Layout laden. Ein einfaches <Script src=".../gtag/js"> im Layout läuft vor jeder Wahl. Lade es stattdessen aus dem Consent-Status.
    • Das Smartphone vergessen. Ein Einstellungsdialog mit vier Kategorien und Cookie-Tabellen passt nicht auf einen 390px breiten Bildschirm. Deshalb wird das Panel unter 768px zum Bottom Sheet. Der Beitrag zum Responsive Dialog behandelt dieses Muster.
    • Unterschiedliche Versionen. Wenn der Banner Version 2 nutzt und GoogleTag den Standard 1, hält der Tag den Besucher für unentschieden. Nutz eine einzige Konstante.
    • Funktions-Props aus dem Layout. onDecide und die Funktions-Labels (showCookies, gateTitle) können ein Server-Layout nicht verlassen. Überschreib dort nur die String-Labels, oder pack den Banner in eine kleine Client-Komponente, der die Funktionen gehören.

    Was solltest du als Nächstes tun?

    Installier den Banner mit npx wingo-ui@latest add cookie-consent, ergänz das Script mit den Standardwerten und die Komponente GoogleTag, und pack jedes Embed eines Drittanbieters in ConsentGate. Komponente und Hook gehören beide zu Wingo UI Pro; die Preisseite zeigt, was jeder Plan enthält. Weitere Guides zu App Router und Tailwind v4 findest du unter Next.js und Tailwind CSS, am besten zuerst der Guide zu Design Tokens in Tailwind v4, aus dem die Farben des Banners kommen.

    Komponenten in diesem Artikel

    • Cookie Consent

      A React cookie consent banner for GDPR: a bottom bar or card with Accept, Reject and Settings, per-category switches, and a gate for embeds.

      Pro
    • useConsent

      A React cookie consent hook: which categories a visitor allowed, when and under which policy version, shared by banner, links and scripts.

      Pro

    Häufige Fragen

    Braucht ein Cookie-Banner einen Button „Alle ablehnen“?

    In der EU sehen die Aufsichtsbehörden es als Problem, wenn auf der Ebene mit dem Zustimmen-Button keine Ablehnung möglich ist. Laut dem Bericht der EDPB Cookie Banner Taskforce vom 17. Januar 2023 werteten die allermeisten Behörden das als Verstoß, also gehört „Alle ablehnen“ in gleicher Größe neben „Alle akzeptieren“.

    Wie richte ich Google Consent Mode v2 in Next.js ein?

    Leg in app/layout.tsx ein Inline-Script mit next/script und strategy beforeInteractive an, das gtag definiert und gtag('consent', 'default', ...) aufruft, mit ad_storage, ad_user_data, ad_personalization und analytics_storage auf denied. Ruf dann gtag('consent', 'update', ...) aus einer Client-Komponente auf, sobald der Besucher entscheidet, und bei jedem späteren Besuch, sobald die gespeicherte Wahl gelesen ist.

    Kann ich die Cookie-Einwilligung in localStorage statt in einem Cookie speichern?

    Ja: useConsent und CookieConsent akzeptieren storage local oder session. Der voreingestellte Cookie hat zwei Vorteile: Server-Code kann ihn mit cookies() aus next/headers lesen, und er läuft von selbst ab, sodass der Besucher nach maxAgeDays (standardmäßig 180) erneut gefragt wird.

    Warum blitzt mein Cookie-Banner bei Besuchern auf, die schon gewählt haben?

    Eine statische Seite kennt die gespeicherte Wahl nicht, also erscheint ein auf dem Server gerenderter Banner bei allen, bis der Client den Speicher liest. Render den Banner erst nach der Hydration und nur, solange der Besucher noch nicht entschieden hat; genau dafür gibt es das Flag hydrated von useConsent.

    Brauchen unbedingt notwendige Cookies eine Einwilligung?

    Cookies, die der Dienst zum Funktionieren braucht, etwa ein Session-Cookie oder der Cookie, der die Einwilligung selbst speichert, brauchen nach den ePrivacy-Regeln keine Einwilligung. Analyse- oder Werbe-Cookies werden nicht dadurch ausgenommen, dass du sie „notwendig“ nennst, und der Bericht der EDPB Taskforce zählt falsch eingestufte notwendige Cookies zu den Praktiken, die die Behörden geprüft haben.

    • Next.js
    • DSGVO
    • Cookie-Einwilligung
    • Google Consent Mode
    • Datenschutz
    • App Router

    Teilen

    SR

    Über den Autor

    Serban Rusu

    Gründer von Wingo UI

    Serban Rusu hat Wingo UI gegründet. Er baut die Komponenten, die CLI und den MCP-Server und schreibt über React-UIs für Smartphones und KI-Agents.

    Mehr von Serban
    Weiterlesen

    Ähnliche Artikel

    Alle Artikel
    Next.js und Tailwind CSS

    Nextjs JSON LD: typisiertes Schema, das zur Seite passt

    Nextjs JSON LD im App Router: eine sichere Script-Komponente, typisierte schema-dts-Builder und ein CI-Check, der anschlägt, sobald Markup und Seite abweichen.

    SRSerban Rusu·16. Okt. 2026·10 Min. Lesezeit
    Next.js und Tailwind CSS

    React Controllable State Hook: Code und 3 Fallstricke

    Bau einen React Controllable State Hook, der value oder defaultValue annimmt. Mit komplettem TypeScript-Code, drei echten Fallstricken und dem Radix-Vergleich.

    SRSerban Rusu·16. Okt. 2026·11 Min. Lesezeit
    Next.js und Tailwind CSS

    next-themes Hydration Error: Flackern in Next.js beheben

    Behebe den next-themes Hydration Error und das Theme-Flackern im Next.js App Router. Plus: ein Theme Toggle, der schon beim ersten Paint richtig aussieht.

    SRSerban Rusu·13. Okt. 2026·8 Min. Lesezeit
    Newsletter

    Neue Artikel per E-Mail

    Neue Guides, Tutorials und Vergleiche aus dem Wingo UI Blog, sobald sie erscheinen. Die meisten Artikel sind auf Englisch.

    Kein Spam. Du kannst dich jederzeit abmelden.

    WingoUI

    Animierte, konfigurierbare, mobile-first React-Komponenten und Blöcke. Kopier den Quellcode, pass ihn an und lass deinen Coding-Agenten damit bauen.

    Produkt

    • Komponenten
    • Vorlagen
    • Preise
    • KI-Agenten
    • Theme-Generator

    Ressourcen

    • Doku
    • Blog
    • Changelog
    • Changelog-RSS
    • llms.txt

    Rechtliches

    • AGB
    • Datenschutz
    • Rückerstattung
    • Lizenz
    • Impressum

    Komponenten-Kategorien

    • Buttons & Actions
    • Inputs
    • Forms
    • Navigation
    • Overlays
    • Feedback
    • Data Display
    • Tables & Lists
    • Charts & Stats
    • Layout
    • Media
    • AI Kit
    • Text & Effects
    • Mobile
    • Commerce
    • Marketing Sections
    • Blocks
    • Hooks & Utilities

    © 2026 Wingo UI

    Wingo UI