# Cookie-Einwilligung in Single-Page-Apps richtig umsetzen

> Cookie-Einwilligung in React, Vue und anderen SPAs: gemeinsamer Consent-Status, Routen-Tracking, SSR-Hydration, blockierte Embeds und sofortiger Widerruf.

Source: https://www.cookiesh.eu/de/blog/consent-in-single-page-apps  
Published: 2026-08-11

## Key takeaways

- In einer Single-Page-App ist die Einwilligung Anwendungsstatus: einmal speichern, mit jeder Komponente teilen und ohne Neuladen auf Änderungen reagieren.
- Setzen Sie jede nicht notwendige Kategorie standardmäßig auf abgelehnt, damit Code, der vor dem Laden der gespeicherten Entscheidung läuft, keine Daten preisgeben kann.
- Senden Sie virtuelle Seitenaufrufe bei Routenwechseln nur, wenn der Besucher Analysen akzeptiert hat.
- Lesen Sie das Consent-Cookie beim serverseitigen Rendern aus oder rendern Sie den Banner nur auf dem Client, um Hydration-Mismatches und ein Flackern des Banners zu vermeiden.
- Der Widerruf muss so einfach sein wie die Einwilligung, und ein Neuladen der Seite ist oft der sauberste Weg, bereits laufende Skripte zu stoppen.

In einer Single-Page-App (SPA) wird die Seite nie vollständig neu geladen. Die Cookie-Einwilligung muss deshalb im Anwendungsstatus leben statt in einem Skript, das einmal läuft. Speichern Sie die Entscheidung des Besuchers, teilen Sie sie mit jeder Komponente, aktivieren oder blockieren Sie Skripte, wenn sie sich ändert, senden Sie Seitenaufrufe bei Routenwechseln nur mit Einwilligung und sorgen Sie dafür, dass ein Widerruf sofort wirkt und nicht erst beim nächsten Seitenaufruf.

## Warum ist die Cookie-Einwilligung in einer Single-Page-App schwieriger?

Die meisten Consent-Muster gehen von vollständigen Seitenaufrufen aus, während eine SPA einmal lädt und danach Ansichten per JavaScript austauscht. Damit fallen vier Annahmen weg:

- **Skripte laden nur einmal.** Wurde Analytics beim Laden blockiert und akzeptiert der Besucher später, gibt es keinen Seitenaufruf, der es nachlädt. Sie müssen es an Ort und Stelle aktivieren.
- **Seitenaufrufe sind virtuell.** Routenwechsel laden die Seite nicht neu. Ihr Router muss also Seitenaufrufe melden und dabei zuerst die Einwilligung prüfen.
- **Entscheidungen ändern sich mitten in der Sitzung.** Ein Besucher kann akzeptieren, weitersurfen und dann über einen Link im Footer widerrufen, während bereits Skripte im Speicher laufen.
- **Serverseitiges Rendern bringt Hydration mit sich.** Bei Frameworks wie Next.js oder Nuxt muss das HTML des Servers mit dem ersten Rendern auf dem Client übereinstimmen, doch der Server kennt die Entscheidung des Besuchers möglicherweise nicht.

## Wie sollte ich den Consent-Status in einer SPA modellieren?

Behandeln Sie die Einwilligung als kleinen Store, aus dem jeder Teil der App liest, mit einer subscribe-Methode, damit Komponenten und Skripte auf Änderungen reagieren. Setzen Sie jede nicht notwendige Kategorie standardmäßig auf abgelehnt, damit Code, der vor dem Laden der gespeicherten Entscheidung läuft, niemals Daten preisgeben kann.

```ts
// consent-store.ts
export type Category = 'necessary' | 'preferences' | 'analytics' | 'marketing';
export type Consent = Record<Category, boolean>;

export const DENIED: Consent = {
  necessary: true, preferences: false, analytics: false, marketing: false,
};

let state: Consent = DENIED;
const listeners = new Set<() => void>();

export const consentStore = {
  get: (): Consent => state,
  set(next: Partial<Consent>) {
    state = { ...state, ...next, necessary: true };
    listeners.forEach((notify) => notify());
  },
  subscribe(listener: () => void) {
    listeners.add(listener);
    return () => { listeners.delete(listener); };
  },
};
```

In einer echten App würde `set` die Entscheidung außerdem in einem First-Party-Cookie speichern, sie in Ihrem Einwilligungsprotokoll festhalten und Google Consent Mode mit `gtag('consent', 'update', …)` aktualisieren. In React verbindet `useSyncExternalStore` den Store mit den Komponenten, und derselbe Hook kann Embeds absichern, die Cookies setzen:

```tsx
// consent.tsx
import { useSyncExternalStore } from 'react';
import { consentStore, DENIED } from './consent-store';

export function useConsent() {
  return useSyncExternalStore(consentStore.subscribe, consentStore.get, () => DENIED);
}

export function VideoEmbed({ videoId }: { videoId: string }) {
  const { marketing } = useConsent();

  if (!marketing) {
    return (
      <div className="embed-placeholder">
        <p>Dieses Video wird von YouTube bereitgestellt, das Marketing-Cookies setzen kann.</p>
        <button type="button" onClick={() => consentStore.set({ marketing: true })}>
          Marketing-Cookies erlauben und abspielen
        </button>
      </div>
    );
  }

  return (
    <iframe
      src={`https://www.youtube-nocookie.com/embed/${videoId}`}
      title="Produktvideo"
      allowFullScreen
    />
  );
}
```

Das dritte Argument von `useSyncExternalStore` ist der Snapshot, der beim serverseitigen Rendern und bei der Hydration verwendet wird. Wenn Sie dort `DENIED` zurückgeben, stimmen Server und erstes Rendern auf dem Client überein, und React rendert direkt nach der Hydration mit dem tatsächlichen Status neu. In Vue übernimmt ein Composable, das ein reaktives `ref` kapselt, dieselbe Rolle.

Skripte, die keine Komponenten sind, abonnieren den Store direkt und werden per dynamischem `import()` geladen, sobald ihre Kategorie erlaubt ist. Unser Leitfaden zum [Blockieren von Skripten bis zur Einwilligung](/de/blog/block-scripts-until-consent) behandelt die Aktivierungsmuster.

## Wie tracke ich Routenwechsel nur mit Einwilligung?

Senden Sie nach jeder Navigation einen virtuellen Seitenaufruf aus dem Router und prüfen Sie die Einwilligung in genau diesem Moment. Erteilt der Besucher mitten in der Sitzung die Einwilligung für Analysen, ist es vertretbar, die aktuelle Seite zu erfassen, denn dort beginnt das Opt-in.

```tsx
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useConsent } from './consent';
import { trackPageView } from './analytics';

export function PageViewTracker() {
  const { pathname } = useLocation();
  const { analytics } = useConsent();

  useEffect(() => {
    if (analytics) trackPageView(pathname);
  }, [pathname, analytics]);

  return null;
}
```

Achten Sie auf Doppelzählungen. Manche Analyse-Tools können bei Änderungen des Browserverlaufs automatisch Seitenaufrufe senden; Google Analytics 4 bietet das als Option der erweiterten Messung an. Nutzen Sie entweder die automatische Option oder Ihren eigenen Tracker, nicht beides.

## Wie vermeide ich Hydration-Mismatches und ein Flackern des Banners?

Legen Sie fest, wo die Entscheidung des Besuchers beim ersten Rendern bekannt ist. Es gibt zwei solide Optionen:

- **Das Consent-Cookie auf dem Server auslesen.** Cookies werden mit jedem Request gesendet, sodass serverseitig gerenderte Seiten den Banner vom ersten Byte an korrekt anzeigen oder weglassen können. Das funktioniert nur, wenn das HTML nicht gecacht und zwischen Besuchern geteilt wird.
- **Den Banner nur auf dem Client rendern.** Bei statisch generierten oder per CDN gecachten Seiten rendern Sie auf dem Server nichts und zeigen den Banner, nachdem die App gemountet ist. Positionieren Sie ihn als fixiertes Overlay, damit ein spätes Erscheinen keinen Layout Shift verursacht; siehe [Cookie-Banner und Core Web Vitals](/de/blog/cookie-banners-core-web-vitals).

In beiden Fällen gilt das Cookie oder der Speichereintrag, der sich die Entscheidung merkt, in der Regel als notwendig, um diese Entscheidung zu respektieren; siehe [was als unbedingt erforderliches Cookie gilt](/de/blog/strictly-necessary-cookies).

## Wie gehe ich mit einem Widerruf mitten in der Sitzung um?

Sorgen Sie dafür, dass ein Widerruf sofort wirkt und so einfach ist wie die Einwilligung, wie es Artikel 7 Absatz 3 DSGVO verlangt. In der Praxis heißt das: ein dauerhaft verfügbarer Link „Cookie-Einstellungen“, meist im Footer, der die Einstellungen ohne Seitenaufruf erneut öffnet. Wenn ein Besucher eine Kategorie widerruft:

1. Aktualisieren Sie den Store und speichern Sie die neue Entscheidung.
2. Wenn Sie Google-Tags nutzen, senden Sie `gtag('consent', 'update', …)` mit den betreffenden Signalen auf `'denied'`.
3. Rufen Sie keine Tracking-Funktionen mehr auf; über den Store abgesicherter Code erledigt das automatisch.
4. Löschen Sie First-Party-Cookies, die die widerrufene Kategorie gesetzt hat, sofern Sie deren Namen und Domains kennen. Cookies, die auf anderen Domains gesetzt wurden, können Ihre Skripte nicht löschen.
5. Laden Sie die Seite neu, wenn ein widerrufenes Skript bereits läuft. Bibliotheken halten Timer, Listener und IDs im Speicher, und ein Neuladen ist der einzige zuverlässige Weg, sie zu entfernen.

Ein Neuladen nach dem Widerruf ist ein fairer Kompromiss: Er kommt selten vor, und die Alternative wäre, Code auszuführen, für den Sie keine Erlaubnis mehr haben.

## Fazit

In einer SPA ist die Einwilligung Status: standardmäßig ablehnen, einen gemeinsamen Store teilen, Skripte und Embeds darüber absichern, Seitenaufrufe nur mit Einwilligung senden und Widerrufe sofort wirksam machen. Tools wie Cookiesh können Ihnen einen Großteil dieser Infrastruktur abnehmen, mit einem React-SDK, das `CookieshProvider` und einen `useConsent()`-Hook bereitstellt, sowie einem `consent:updated`-Event für alles andere.

*Dieser Artikel dient der allgemeinen Information und stellt keine Rechtsberatung dar. Für Entscheidungen zu Ihrer konkreten Situation wenden Sie sich an qualifizierte Datenschutzfachleute.*

## FAQ

### Muss ich den Cookie-Banner nach jedem Routenwechsel erneut anzeigen?

Nein. Zeigen Sie ihn an, bis der Besucher eine Wahl trifft, speichern Sie diese Wahl und stellen Sie einen dauerhaft verfügbaren Link zu den Einstellungen bereit. Fragen Sie erst dann erneut, wenn sich Ihre Zwecke oder Anbieter wesentlich ändern oder die gespeicherte Entscheidung abläuft.

### Kann ich die Consent-Entscheidung statt in einem Cookie im localStorage speichern?

Beides funktioniert im Browser, aber ein Cookie wird mit jedem Request auch an Ihren Server gesendet, sodass serverseitig gerenderte Seiten schon beim ersten Rendern den richtigen Status anzeigen können. Den localStorage kann nur clientseitiger Code auslesen, nachdem die App geladen ist.

### Kümmern sich Script-Komponenten von Frameworks wie next/script um die Einwilligung?

Nein. Hilfsmittel wie die Script-Komponente in Next.js steuern, wann ein Skript im Verhältnis zur Hydration geladen wird, nicht ob der Besucher ihm zugestimmt hat. Rendern Sie sie abhängig von Ihrem Consent-Status, damit sie vor einem Opt-in nicht zur Seite hinzugefügt werden.

## Sources

- [Directive 2002/58/EC (ePrivacy Directive), EUR-Lex](https://eur-lex.europa.eu/eli/dir/2002/58/oj)
- [Regulation (EU) 2016/679 (GDPR), EUR-Lex](https://eur-lex.europa.eu/eli/reg/2016/679/oj)
- [Google: Überblick über den Consent Mode](https://developers.google.com/tag-platform/security/guides/consent)
