bfcache optimieren: Eligibility & Core Web Vitals (2026)

Wie Sie den Back/Forward Cache prüfen, häufige Blocker wie no-store und unload-Handler beheben und damit LCP, INP und FCP messbar verbessern.

bfcache Guide: Core Web Vitals (2026)

Aktualisiert: 28. Juni 2026

Der Back/Forward Cache (bfcache) ist ein In-Memory-Snapshot der kompletten Seite (DOM, JavaScript-Heap und Netzwerk-Verbindungen), den Browser anlegen, sobald die Nutzerin oder der Nutzer wegnavigiert. Klicken sie auf "Zurück" oder "Vorwärts", wird die Seite in der Regel in unter 100 Millisekunden wiederhergestellt; LCP, FCP und INP fallen praktisch auf null. Ehrlich gesagt: In CrUX und in echten RUM-Daten ist bfcache der größte Einzelhebel, den Sie mit wenig Aufwand bewegen können. Vorausgesetzt, Ihre Seite ist überhaupt eligible.

  • bfcache-Treffer liefern sub-100ms-Navigation und bringen LCP, FCP und INP für diesen Page Load praktisch auf null — die größte CWV-Verbesserung pro investierter Stunde.
  • Die Eligibility prüfen Sie in Chrome DevTools unter Application → Background services → Back/forward cache und produktiv über die NotRestoredReasons API im pageshow-Event.
  • Der häufigste Blocker im Feld ist Cache-Control: no-store, oft unbeabsichtigt durch Auth-Middleware gesetzt; ohne Anpassung greift bfcache in Chrome nie und in Firefox nur eingeschränkt.
  • Weitere typische Blocker: unload-Handler (durch pagehide ersetzen), offene WebSockets/IndexedDB-Transaktionen, Permissions-API-Holds und window.opener-Beziehungen.
  • CrUX exponiert seit 2024 die Dimension navigation_types mit back_forward_cache: damit messen Sie Ihre Hit-Rate gegen Branchenmedian (~30–60% bei gut optimierten Seiten).
  • Auch der Bounce-Rate-Effekt ist real: bfcache-Treffer reduzieren laut Chrome-Team Aufgaben pro Session messbar, weil Nutzer schneller zurückkehren statt erneut zu suchen.

Was ist der Back/Forward Cache?

Der Back/Forward Cache speichert eine vollständig initialisierte Seite (inklusive JavaScript-Heap, Scroll-Position, Formularzuständen und laufenden Timern) im Speicher des Browsers, sobald Sie auf einen Link klicken oder ein neues Ziel eingeben. Navigieren Sie zurück, wird der Snapshot reaktiviert, statt das HTML neu zu parsen, JS neu zu evaluieren und Hydration erneut auszuführen. Chrome implementiert bfcache seit Version 96 standardmäßig, Firefox bereits deutlich länger und Safari seit jeher.

In meiner Arbeit mit RUM-Daten von News- und E-Commerce-Sites sehe ich Hit-Raten zwischen 0 % (Auth-lastige Apps mit no-store) und über 70 % (saubere Content-Sites). Der Unterschied im 75. Perzentil von LCP und INP ist drastisch: bfcache-Navigationen ziehen den gesamten p75 nach unten, weil sie als regulärer Page Load in CrUX gezählt werden. Wer Core Web Vitals optimiert, ohne bfcache zu prüfen, lässt den größten Hebel ungenutzt. Mehr Kontext zu den drei CWV-Metriken finden Sie im Core-Web-Vitals-Leitfaden 2026.

Wie wirkt sich bfcache auf Core Web Vitals aus?

Bei einer bfcache-Restoration meldet der Browser PerformanceNavigationTiming.type = "back_forward" und feuert pageshow mit event.persisted = true. Web-Vitals-Bibliotheken wie web-vitals.js erfassen ab Version 3 diese Restorations als eigene Datenpunkte und liefern für LCP typischerweise Werte unter 100 ms, weil der Largest Contentful Paint dem pageshow-Zeitpunkt entspricht.

Konkret beobachten wir folgende Effekte: LCP sinkt auf den Zeitraum zwischen pageshow und dem ersten Frame, in Tests typischerweise 30–80 ms. INP profitiert ebenfalls, weil keine Hydration läuft und der Main Thread frei ist; der erste Klick nach einer bfcache-Restoration kostet kaum Verarbeitungszeit. CLS akkumuliert ab Restoration neu. Wenn Sie aber Ihre Layout-Stabilität ohnehin im Griff haben, gibt es nichts zu verlieren. Eine systematische Auseinandersetzung mit Layoutsprüngen finden Sie ergänzend im LoAF-Leitfaden für INP-Debugging.

bfcache-Hit-Rate in CrUX und RUM messen

CrUX exponiert seit 2024 in der BigQuery-Tabelle und über die History-API die Dimension navigation_types. Diese unterscheidet zwischen navigate, reload, back_forward und back_forward_cache. Letzteres ist der bfcache-Treffer; das Verhältnis zwischen back_forward_cache und der Summe aus back_forward + back_forward_cache ist Ihre Hit-Rate für historische Navigationen.

In eigener RUM-Instrumentierung erfassen Sie das so. Diesen Snippet können Sie eins zu eins in einem Custom-RUM-Beacon einsetzen:

// Bei pageshow prüfen, ob es sich um eine bfcache-Restoration handelt
window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // bfcache-Treffer – als eigene Navigation zählen
    navigator.sendBeacon('/rum', JSON.stringify({
      type: 'bfcache_restore',
      url: location.pathname,
      // Optional: web-vitals 3.x erfasst LCP/INP/CLS auch hier
    }));
  }
});

// Zusätzlich: Navigation Type vom PerformanceNavigationTiming
const navEntry = performance.getEntriesByType('navigation')[0];
if (navEntry && navEntry.type === 'back_forward') {
  // Klassische Back/Forward-Navigation OHNE bfcache (Miss)
  console.log('bfcache MISS – Seite wurde neu geladen');
}

Aggregieren Sie diese Beacons pro URL-Pattern und Device-Klasse. In meiner Praxis variiert die Hit-Rate stark zwischen Mobile und Desktop, weil mobile Browser bei Speicherknappheit aggressiver evicten. Veröffentlichen Sie den 75. Perzentilwert pro Pattern, und Sie sehen sofort, welche Routen kaputt sind. Wenn die Server-Antwortzeit die nächste Baustelle ist, hilft der TTFB-Optimierungsleitfaden weiter.

Eligibility in Chrome DevTools prüfen

Der schnellste Weg, eine konkrete Seite zu prüfen: DevTools öffnen, auf Application → Background services → Back/forward cache navigieren und auf Test back/forward cache klicken. Chrome navigiert dann zu chrome://terms und zurück und meldet, ob die Seite eligible war. Falls nicht, bekommen Sie eine kategorisierte Liste aller Gründe:

  • Circumstantial: temporäre Gründe (z. B. Cache-Speicher voll), nicht durch Sie behebbar.
  • PageSupportNeeded: Browser-Features, die noch keinen bfcache-Support haben. Wird mit Chrome-Updates besser.
  • SupportPending: wird gerade implementiert.
  • Actionable: das sind Ihre Hausaufgaben. Hier liegt der gesamte Hebel.

Konzentrieren Sie sich ausschließlich auf "Actionable". Typische Einträge: MainResourceHasCacheControlNoStore, UnloadHandlerExistsInMainFrame, HasActiveWebSocket, BroadcastChannel. Jeder dieser Punkte hat eine konkrete Behebung, die ich unten in Häufige Blocker beschreibe. Wichtig: DevTools testet nur den aktuellen Zustand. Ein leeres Suchformular kann eligible sein, während die ausgefüllte Variante eine Worker-Connection öffnet und blockiert wird. Testen Sie deshalb realistische User-Flows.

NotRestoredReasons API im Feld auswerten

DevTools ist gut für die Werkbank, aber Sie brauchen Felddaten. Die NotRestoredReasons API (seit Chrome 124 stabil) liefert genau das: Im pageshow-Event können Sie ablesen, warum die Restoration ggf. fehlschlug. Ergänzend dokumentiert die MDN-Referenz zu notRestoredReasons alle Reason-Codes.

// Funktioniert auch bei MISS (event.persisted === false)
window.addEventListener('pageshow', () => {
  const navEntry = performance.getEntriesByType('navigation')[0];
  const reasons = navEntry?.notRestoredReasons;

  if (!reasons) return; // Erfolg oder API nicht unterstützt

  // reasons.reasons enthält Array von { reason: string }
  const reasonCodes = (reasons.reasons || []).map(r => r.reason);

  // Beispiele: 'unload-handler', 'response-cache-control-no-store',
  // 'broadcastchannel-message', 'websocket'
  navigator.sendBeacon('/rum/bfcache-miss', JSON.stringify({
    url: location.pathname,
    reasons: reasonCodes,
    children: (reasons.children || []).map(c => ({
      src: c.src,
      reasons: (c.reasons || []).map(r => r.reason),
    })),
  }));
});

Der zweite, oft vergessene Teil ist children: iframes können den bfcache des Top-Frames blockieren, etwa wenn ein Drittanbieter-Widget einen WebSocket offen hält. Ich habe genau diesen Fall einmal in einem Newsroom-Projekt gesehen, wo Marketing über Nacht ein Chat-Widget einbaute und die Hit-Rate von 65 % auf 12 % einbrach. Wenn Sie nur die Top-Level-Reasons betrachten, übersehen Sie solche Regressionen. Ähnliche Drittanbieter-Probleme behandle ich ausführlich im Artikel zu Third-Party-Scripts.

Häufige Blocker und wie Sie sie beheben

Cache-Control: no-store

Der mit Abstand häufigste Blocker. Cache-Control: no-store auf dem Haupt-HTML-Dokument verhindert in Chrome jeden bfcache-Treffer; Firefox erlaubt nur eingeschränkten Support. Viele Auth-Frameworks setzen den Header pauschal, um Cookie-Daten zu schützen. Das ist meistens überdimensioniert: Cache-Control: private, no-cache reicht in 95 % der Fälle und ist bfcache-kompatibel. Reservieren Sie no-store für tatsächlich sensible Seiten wie das Banking-Cockpit, nicht für jede angemeldete Produktseite.

unload-Event-Handler

Jeder window.addEventListener('unload', ...) (und genauso onunload als Inline-Attribut) disqualifiziert die Seite. Das gilt auch für indirekte Quellen wie Analytics-Snippets. Migrieren Sie alles auf pagehide (mit event.persisted-Check) oder visibilitychange. Das funktioniert ohnehin zuverlässiger, weil unload auf Mobile-Browsern nicht garantiert feuert.

Offene Verbindungen: WebSocket, WebRTC, IndexedDB-Transaktionen

Aktive WebSocket-Verbindungen oder laufende IndexedDB-Transaktionen halten die Seite "live"; der Browser kann nicht garantieren, dass ein Snapshot konsistent bleibt. Lösung: Schließen Sie Verbindungen in pagehide und öffnen Sie sie in pageshow wieder. Für Chats und Live-Daten ist das ohnehin der bessere UX-Default, weil sonst stille Zombie-Verbindungen entstehen.

BroadcastChannel

Ähnlich wie WebSockets: ein offener BroadcastChannel blockiert. Schließen Sie ihn in pagehide mit channel.close().

Permissions-API-Holds und Sensor-Streams

Aktive Geolocation-Watcher, MediaStream-Tracks oder Permission-Prompts blockieren. Räumen Sie mit navigator.geolocation.clearWatch(id) und track.stop() auf, sobald sie nicht mehr gebraucht werden.

Das pageshow/pagehide-Pattern korrekt implementieren

Wenn bfcache greift, läuft Ihr Initialisierungs-Code (z. B. DOMContentLoaded) nicht erneut. Alles, was zwischen pagehide und pageshow veraltet sein könnte (Warenkorbzähler, eingeloggter Username, ungelesene Notifications) muss explizit aktualisiert werden. Das kanonische Muster:

// Initialisierung (läuft nur beim ersten Load)
init();

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // bfcache-Restoration: nur das auffrischen, was veralten kann
    refreshCart();
    refreshNotifications();
    reopenWebSocket();
  }
});

window.addEventListener('pagehide', (event) => {
  // Verbindungen schließen, damit bfcache greifen kann
  closeWebSocket();
  if (event.persisted) {
    // Seite WURDE in bfcache aufgenommen – kein Final-Cleanup
  } else {
    // Echter Unload – Analytics-Beacon senden
    navigator.sendBeacon('/rum/end', collectMetrics());
  }
});

Ein häufiger Fehler ist, Analytics nur in beforeunload oder unload zu senden. Verwenden Sie stattdessen visibilitychange auf hidden als primäres Signal und pagehide als Backup. Beide sind bfcache-kompatibel.

bfcache mit React, Next.js und SPAs

SPAs sind nicht automatisch bfcache-fähig. Eine clientseitige Routenänderung erzeugt keinen Browser-Navigationsentry; sie bleibt im selben Document. Das hat zwei Konsequenzen. Erstens greift bfcache nur bei echten Browser-Navigationen weg von Ihrer SPA. Zweitens müssen Sie auf pageshow manuell prüfen, ob Ihre Routing-Bibliothek den aktuellen Pfad noch korrekt darstellt; manche serialisierten Zustände driften.

Für Next.js mit App Router ist die wichtigste Stellschraube der Cache-Control-Header. Wenn Sie dynamic = 'force-dynamic' setzen oder Cookies/Headers im Server Component lesen, setzt Next.js automatisch no-store. Schreiben Sie diesen Default in der Middleware um, wo es sicher ist:

// middleware.ts (Next.js 15)
import { NextResponse } from 'next/server';

export function middleware(request) {
  const res = NextResponse.next();
  // Nur überschreiben, wenn keine sensiblen Daten betroffen sind
  if (!request.nextUrl.pathname.startsWith('/account')) {
    res.headers.set('Cache-Control', 'private, no-cache, max-age=0');
  }
  return res;
}

Für Hydration-Fragen rund um Server-Komponenten ist das JavaScript-Performance-Tutorial die nächste Lektüre.

bfcache mit Auth, Cookies und Paywalls

Das hartnäckigste Missverständnis: "Wir haben Login, also brauchen wir no-store." Das stimmt nicht. Der bfcache liegt im lokalen Speicher des Browsers, nicht in einem Shared-Cache. Er ist per Design pro Tab und pro Session; Daten leaken nicht zwischen Nutzern. Sicherheitsrelevant ist nur, was passiert, wenn Nutzer sich ausloggen und dann auf "Zurück" drücken. Lösung:

  • Beim Logout das BroadcastChannel-Signal an andere Tabs senden; in pageshow auf das Signal hören und sofort weiterleiten.
  • Cookie-Auth-Token serverseitig invalidieren, sodass jeder Folge-Request (etwa beim Aktualisieren des Warenkorbs in pageshow) ohnehin abgewiesen wird.
  • Für echte Hochsicherheits-Routen Cache-Control: no-store selektiv setzen (Account-Übersicht, Zahlungsformulare). Alles andere darf in den bfcache.

Detaillierte Empfehlungen liefert die offizielle web.dev-Dokumentation zum bfcache sowie das Chrome-DevTools-Handbuch für bfcache-Testing.

Häufig gestellte Fragen

Was ist der Unterschied zwischen bfcache und HTTP-Cache?

Der HTTP-Cache speichert Ressourcen (HTML, JS, Bilder) und erlaubt schnelle Reloads, erfordert aber weiterhin Parsing, Hydration und Initial-Rendering. Der bfcache speichert die komplett initialisierte Seite inklusive JavaScript-Heap, sodass Restoration in Mikrosekunden möglich ist, ohne erneutes Rendern.

Warum funktioniert mein bfcache nicht?

In 80 % der Fälle, die ich auditiere, liegt es entweder an Cache-Control: no-store auf dem HTML-Dokument oder an einem unload-Event-Handler. Prüfen Sie beides zuerst, dann die NotRestoredReasons API für die genaue Diagnose pro Seite.

Welche bfcache-Hit-Rate ist gut?

Content-lastige Sites ohne komplexe Auth-Flows erreichen 60–80 % auf dem 75. Perzentil. Auth-lastige Apps liegen oft unter 10 %, sollten aber mit den hier beschriebenen Maßnahmen 30–50 % schaffen. Vergleichen Sie immer pro Device-Klasse, da mobile Browser aggressiver evicten.

Verbessert bfcache die Core Web Vitals?

Ja, direkt und messbar. bfcache-Restorations werden in CrUX als regulärer Page Load gezählt; LCP, FCP und INP liegen typischerweise unter 100 ms, was Ihren p75 spürbar senkt. Bei Sites mit hoher Back-Navigation-Rate (News, E-Commerce-Kategorien) ist es der größte CWV-Hebel überhaupt.

Blockiert Google Analytics oder GTM den bfcache?

Aktuelle Versionen von GA4 und GTM verwenden visibilitychange statt unload und sind bfcache-kompatibel. Ältere Custom-Tracking-Snippets oder selbstgeschriebene Beacons mit unload-Handler sind eine häufige Ursache für Eligibility-Verluste; auditieren Sie alle Third-Party-Tags.

Nadia El-Sayed
Über den Autor Nadia El-Sayed

Core Web Vitals specialist focused on real-user monitoring. Believes synthetic-only perf testing is a comforting lie.