Zum Inhalt springen
Core Web Vitals & Messung

fetchLater: Messdaten senden, ohne Tempo zu kosten

Abschiedsereignisse liefern den letzten Messwert mobil häufig nicht aus. Wie fetchLater() das löst, welches Kontingent gilt und wie der Rückfallweg aussieht.

12 Min. Lesezeit MessungBrowser-APIINP

Jede Messung im Feld endet an derselben Stelle: Der Wert steht fest, aber die Seite ist schon halb verschwunden. Interaction to Next Paint kennt seinen endgültigen Wert erst, wenn keine Interaktion mehr folgt, und Cumulative Layout Shift summiert bis zum letzten Bild. Wer diese Zahlen wirklich haben möchte, muss sie in genau dem Moment abschicken, in dem der Browser am wenigsten geneigt ist, noch eine Netzanfrage zu starten. Die dafür gedachten Abschiedsereignisse sind auf Mobilgeräten unzuverlässig: Chrome for Developers schreibt, dass die Ereignisse manchmal überhaupt nicht ankommen (Chrome for Developers). Mit fetchLater() gibt es ab Chrome 135 einen anderen Weg - die Anfrage wird angemeldet, solange die Seite gesund ist, und der Browser schickt sie selbst ab, wenn das Dokument verschwindet (MDN browser-compat-data). Dieser Text erklärt, wie das Kontingent aufgebaut ist, wo die Grenzen liegen und wie eine Rückfallebene aussieht, die man ohne schlechtes Gewissen ausliefern kann.

Das Wichtigste in Kürze

  • Abschiedsereignisse sind kein verlässlicher Kanal. Auf Mobilgeräten läuft unload häufig nicht, weil Tabs in den Hintergrund wandern und dort beendet werden (Chrome for Developers).
  • fetchLater() dreht die Reihenfolge um: Die Anfrage wird angemeldet, solange die Seite gesund ist, und der Browser schickt sie ab, wenn das Dokument endgültig verschwindet.
  • Das Kontingent liegt bei 640 KiB je Tab, davon 512 KiB für das Hauptdokument und 128 KiB für eingebettete Rahmen fremder Herkunft (WHATWG, MDN).
  • Ausgeliefert ist die Schnittstelle bislang in einer Browserfamilie ab Version 135; für alle anderen bleibt der Beacon am Sichtbarkeitswechsel die Rückfallebene (MDN browser-compat-data).

Warum der letzte Messwert häufig nicht ankommt

Der Ablauf ist in fast jedem Messskript derselbe. Während die Seite offen ist, sammelt ein PerformanceObserver Werte ein. Am Ende soll ein Paket an den eigenen Endpunkt gehen. Dafür gab es historisch zwei Anker: unload und pagehide, später ergänzt um visibilitychange. Chrome for Developers formuliert das Problem in aller Deutlichkeit: Beide Ereignisse hätten schwierige Randfälle, die sich je nach Browser unterscheiden, und manchmal kämen die Ereignisse überhaupt nicht an, besonders auf Mobilgeräten (Chrome for Developers). Der Text zur Abkündigung von unload wird noch konkreter: Auf mobilen Browsern laufe unload oft nicht, weil Tabs häufig in den Hintergrund geschoben und dann beendet würden (Chrome for Developers). Das ist kein Randfall, sondern der Normalfall auf dem Gerät, auf dem die meisten Sitzungen stattfinden.

Dazu kommt, dass unload nicht bloß unzuverlässig ist, sondern aktiv im Weg steht: Ein registrierter Handler verhindert, dass die Seite in den Vor- und Zurück-Zwischenspeicher wandert. Die Verbreitung ist entsprechend rückläufig, aber sie ist nicht verschwunden. Der Web Almanac 2025 zählt bei den 1.000 meistbesuchten Websites unload-Handler auf 28 Prozent der Desktop-Seiten und 20 Prozent der mobilen Seiten; der Anteil sinke über die schlechter platzierten Seiten hinweg stetig und erreiche 11 Prozent auf dem Desktop und 10 Prozent auf Mobilgeräten (Web Almanac 2025). Wer heute noch an diesem Kanal hängt, misst also ausgerechnet die Sitzungen schlecht, die am meisten über die eigene Seite verraten - die langen, die mobilen, die abgebrochenen.

Der alte Kanal wird planmäßig abgeschaltet

Die Abkündigung von unload läuft nicht als Ankündigung, sondern als Zeitplan mit Meilensteinen. In der Tabelle von Chrome for Developers steht für Meilenstein 154 am 22. September 2026 der Eintrag 100 - alle Seiten (Chrome for Developers). Bis dahin wächst der Anteil der Seiten, auf denen der Handler ausbleibt, schrittweise an. Wer Messdaten daran festgemacht hat, sieht das nicht als Fehler, sondern als langsam sinkende Datenmenge. Genau deshalb lohnt der Wechsel jetzt und nicht erst, wenn die Zahlen unerklärlich abknicken.

Was fetchLater() anders macht

Die Idee ist schlicht und deshalb tragfähig: Statt im Moment des Verschwindens noch eine Anfrage abzusetzen, meldet die Seite die Anfrage vorher an. fetchLater() nimmt dieselben Angaben entgegen wie fetch - Adresse, Methode, Kopfzeilen, Nutzlast - schickt aber nichts sofort los. Der Browser merkt sich die Anfrage und führt sie aus, wenn das Dokument zerstört wird. Das verlagert die Verantwortung dorthin, wo sie hingehört: Der Browser weiß, wann ein Tab beendet wird, das Skript weiß es nicht. Erprobt wurde die Schnittstelle ab Chrome 121 im Januar 2024 in einem Ursprungsversuch mit echten Nutzern, der bis zum 3. September 2024 lief (Chrome for Developers).

Zwei Eigenschaften machen den Unterschied im Alltag. Erstens lässt sich eine angemeldete Anfrage vor dem Absenden zurücknehmen und durch eine neue ersetzen. Wer alle paar Sekunden einen aktualisierten Wert für die Interaction to Next Paint hat, meldet nicht zehn Anfragen an, sondern zieht die vorige zurück und meldet eine neue mit dem aktuellen Stand an. Zweitens gibt es activateAfter: eine Frist in Millisekunden, nach deren Ablauf der Browser die Anfrage von sich aus abschickt, auch wenn die Seite noch lebt. Damit lässt sich ein Herzschlag bauen, ohne einen eigenen Zeitgeber zu unterhalten, der bei Hintergrundtabs ohnehin gedrosselt wird.

Das Kontingent: 640 KiB je Tab

Ein Kanal, der nach dem Ende der Seite noch sendet, braucht eine harte Obergrenze - sonst wird er zum Werkzeug für Datenabfluss. Der Fetch Standard legt sie fest: Das Kontingent für zurückgestelltes Abrufen wird einem obersten Navigierbaren zugeteilt, also einem Tab, und beträgt 640 Kibibyte (WHATWG). Davon sind nach Voreinstellung 128 Kibibyte dafür reserviert, das Kontingent an eingebettete Dokumente fremder Herkunft weiterzugeben, wobei jedes davon 8 Kibibyte belegt (WHATWG). MDN beschreibt dieselbe Aufteilung aus Sicht des Dokuments: 640 KiB je Dokument, aufgeteilt in ein Kontingent von 512 KiB für die oberste Ebene und ein geteiltes Kontingent von 128 KiB (MDN). Innerhalb des zugeteilten Kontingents dürfen außerdem nur 64 Kibibyte gleichzeitig auf dieselbe meldende Herkunft entfallen, also auf den Ursprung der Zieladresse (WHATWG).

messwert-vormerken.js
// Einen Messwert vormerken, solange die Seite lebt.
// Der Browser schickt ihn später selbst ab - ohne Abschiedsereignis.
let steuerung = null;
let anmeldung = null;
let nutzlast = null;

function messwertVormerken(daten) {
  nutzlast = JSON.stringify(daten);

  if (typeof fetchLater !== 'function') return;

  // Die vorige Anmeldung zurücknehmen, solange sie nicht abgeschickt ist.
  if (anmeldung && !anmeldung.activated && steuerung) steuerung.abort();

  steuerung = new AbortController();
  try {
    anmeldung = fetchLater('/api/messwerte', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: nutzlast,
      signal: steuerung.signal,
      activateAfter: 300000
    });
  } catch (fehler) {
    // QuotaExceededError: Kontingent aufgebraucht - Nutzlast verkleinern.
    anmeldung = null;
  }
}

// Rückfallebene für Browser ohne fetchLater()
if (typeof fetchLater !== 'function') {
  addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden' && nutzlast) {
      navigator.sendBeacon('/api/messwerte', nutzlast);
    }
  });
}

Der Vergleich mit dem bisherigen Weg fällt deutlich aus. Für keepalive-Anfragen begrenzt der Fetch Standard die Summe: Wenn die Summe aus Inhaltslänge und den bereits laufenden keepalive-Bytes 64 Kibibyte übersteigt, wird ein Netzwerkfehler zurückgegeben (WHATWG). Das ist der Deckel, unter dem navigator.sendBeacon und fetch mit keepalive gemeinsam arbeiten. Zehnmal mehr Platz klingt zunächst nach Luxus, ist aber vor allem eine Einladung, sauber zu bleiben: Eine angemeldete Anfrage hält Speicher, solange sie offen ist. Landet die Seite im Vor- und Zurück-Zwischenspeicher, bleibt die Anmeldung bestehen - die Chromium-Dokumentation nennt als Obergrenze die Lebensdauer dieses Zwischenspeichers, derzeit 10 Minuten, nachdem die Seite hineingewandert ist (Chromium).

Verfügbarkeit und Rückfallebene

Hier liegt der Haken, und er gehört an den Anfang jeder Planung. Die Kompatibilitätsdaten von MDN weisen für fetchLater() bei Chrome die Version 135 als erste ausliefernde Fassung aus (MDN browser-compat-data). Für Firefox steht dort keine Auslieferung, mit Verweis auf den offenen Fehlerbericht; für Safari sieht es genauso aus (MDN browser-compat-data). Praktisch heißt das: Ein erheblicher Teil der mobilen Sitzungen läuft weiterhin ohne diese Schnittstelle. Wer seine Felddaten danach auswertet, ohne die Rückfallebene mitzudenken, verschiebt seine Stichprobe still in Richtung einer Browserfamilie und bekommt danach Zahlen, die über das eigene Publikum wenig aussagen.

Erkennung statt Annahme

Eine Prüfung auf typeof fetchLater === 'function' entscheidet den Weg. Kein Rückschluss aus der Kennung des Browsers - die Kennung sagt über diese Schnittstelle nichts aus, und sie wird ohnehin an vielen Stellen verkürzt ausgeliefert.

Ein Ereignis, zwei Wege

Der Beacon am Sichtbarkeitswechsel bleibt für alle übrigen Browser stehen. Registriert wird er ausschließlich dann, wenn fetchLater() fehlt, sonst käme derselbe Wert doppelt an und müsste in der Auswertung wieder entdoppelt werden.

Dieselbe Nutzlast

Beide Wege senden denselben Rumpf an dieselbe Adresse. Der Endpunkt sieht keinen Unterschied und braucht keine zweite Auswertungslogik für die Core Web Vitals, was den Aufwand auf ein paar Zeilen im Messskript begrenzt.

Merkmalnavigator.sendBeaconfetchLater()
Verbreitungin allen aktuellen Browsernbislang eine Browserfamilie ab Version 135 (MDN browser-compat-data)
Obergrenzegemeinsamer keepalive-Deckel von 64 KiB (WHATWG)640 KiB je Tab (WHATWG)
Auslöserbraucht ein Abschiedsereignis im Skriptder Browser schickt selbst ab
Korrigierbareinmal abgeschickt, unveränderlichAnmeldung bis zum Absenden ersetzbar
Zeitgesteuerteigener Zeitgeber nötigactivateAfter als Frist in Millisekunden

Was das für die Core Web Vitals bedeutet

Interaction to Next Paint ist der Kennwert, der am stärksten unter verlorenen Abschlusspaketen leidet, weil sein Endwert erst am Sitzungsende feststeht. Ein Wert von höchstens 200 ms gilt als gut: Ein INP unterhalb oder bei 200 Millisekunden bedeutet, dass eine Seite gute Reaktionsfähigkeit hat (web.dev). Der Web Almanac 2025 meldet für mobile Seiten eine erfreuliche Verbesserung mit 77 Prozent der Websites im guten Bereich, gegenüber 74 Prozent im Jahr 2024 (Web Almanac 2025). Diese Zahlen stammen aus Feldbeobachtung; die eigene Messung muss dieselbe Qualität erreichen, sonst vergleicht man einen sauberen Außenwert mit einem löchrigen Innenwert. Wer Ausreißer sucht, findet sie über lange Animationsbilder - allerdings nur, wenn das Paket mit dem Ausreißer auch ankommt.

Der Kern in einem Satz

fetchLater() verlegt den Absendezeitpunkt aus dem Skript in den Browser - und damit aus dem Moment, in dem am wenigsten funktioniert, in einen, den der Browser selbst kontrolliert.

Wenn fremde Rahmen am selben Kontingent ziehen

Das geteilte Kontingent ist der Punkt, an dem eine gut gemeinte Einbindung die eigene Messung beschädigt. MDN beschreibt es so: Jeder Unterrahmen fremder Herkunft erhalte nach Voreinstellung 8 KiB aus dem geteilten Kontingent von 128 KiB, zugeteilt beim Einfügen in das Dokument - unabhängig davon, ob fetchLater() dort überhaupt genutzt werde. Das bedeute, dass im Allgemeinen nur die ersten 16 solcher Rahmen die Schnittstelle nutzen könnten, weil sie das Kontingent von 128 KiB aufbrauchen (MDN). Eine Seite mit vielen eingebetteten Videos, Karten und Werbeplätzen verteilt ihr Kontingent also, bevor irgendjemand eine Anfrage anmeldet. Wer ohnehin fremde Skripte entschlackt, gewinnt hier ein zweites Mal.

Ein erschöpftes Kontingent meldet sich als Ausnahme

Reicht der Platz nicht, wirft fetchLater() einen QuotaExceededError. Ohne try und catch stirbt an dieser Stelle die gesamte Funktion, in der die Anmeldung steht - und damit oft der Rest des Messskripts. Deshalb gehört die Anmeldung in einen Fangblock, und der Fangblock braucht einen Plan: kleinere Nutzlast, weniger Felder, im Zweifel der Beacon. Eine Ausnahme, die niemand fängt, kostet mehr Daten als die Grenze selbst.

So gehen wir in Projekten vor

Der Einbau ist klein, die Reihenfolge entscheidet. Wir beginnen mit der Frage, welche Werte überhaupt erst am Sitzungsende feststehen, und trennen sie von allem, was früher gesendet werden kann. Alles, was früher gesendet werden kann, wird auch früher gesendet - das ist die billigste Verbesserung und braucht keine neue Schnittstelle. Erst der Rest kommt in die Anmeldung. Danach wird gemessen, wie viele Pakete tatsächlich ankommen, und zwar getrennt nach Browserfamilie, weil sonst die Verbesserung in einer Familie die Lücke in einer anderen zudeckt. Denselben Zuschnitt nutzen wir, wenn wir mit Server-Timing die Zeit im Backend sichtbar machen - erst die Messkette prüfen, dann die Zahlen deuten.

  1. Werte trennen: Was steht früh fest, was erst am Ende? Ausschließlich der Rest braucht einen Kanal für den Abschied.
  2. Erkennung einbauen und beide Wege verdrahten, ohne dass ein Browser beide zugleich benutzt.
  3. Nutzlast schrumpfen: kurze Feldnamen, keine wiederholten Kontextdaten, Zahlen statt Zeichenketten.
  4. Zustellquote je Browserfamilie erheben, bevor irgendeine Zahl aus dem neuen Kanal in einen Bericht wandert.
  5. Grenzen anfassen: einen Rahmen fremder Herkunft testweise entfernen und prüfen, ob die Anmeldung dann durchgeht.

Für die Zustellquote reicht ein Feld im Paket, das die Sitzung eindeutig macht, und ein zweiter, früher Ping mit derselben Kennung. Das Verhältnis beider Zahlen ist die Zustellquote, und sie ist die einzige belastbare Aussage darüber, wie viel die eigene Messung tatsächlich sieht. Wer diese Quote nicht kennt, kennt auch den Wert seiner Kennzahlen nicht. Das gilt genauso für Anwendungen mit weichen Navigationen: Dort endet das Dokument selten, dafür wechselt der Bildschirminhalt ständig, und die Zuordnung braucht eine eigene Betrachtung über weiche Navigationen.

Der Browser weiß, wann eine Seite endet. Das Skript erfährt es bestenfalls zufällig. Wer den Absendezeitpunkt dorthin verlegt, wo das Wissen liegt, braucht kein Glück mehr.

Projekterfahrung aus Messprojekten für Felddaten

Bleibt die Frage, ob sich der Aufwand lohnt, solange eine einzige Browserfamilie die Schnittstelle ausliefert. Die Antwort hängt am Publikum: Ist der Anteil dieser Familie hoch, hebt der Einbau die Zustellquote spürbar an, ohne dass für die übrigen etwas schlechter wird. Ist er niedrig, bleibt der Einbau trotzdem günstig, weil er weniger als fünfzig Zeilen kostet und die Rückfallebene ohnehin gebraucht wird. Teuer wird nur die Variante, in der man beides halb macht. Wer die Zustellung als Größe behandelt, die man kennt und pflegt, findet dieselbe Haltung im Umgang mit ungenutztem CSS wieder: erst messen, was wirklich passiert, dann entfernen, was niemand braucht.

Quellen und Studien

Dieser Artikel stützt sich auf den Fetch Standard der WHATWG (Abschnitte zum zurückgestellten Abrufen und zu keepalive, Stand 25.08.2026), auf MDN Web Docs mit dem Leitfaden Using Deferred Fetch (Stand 12.01.2026) und den Kompatibilitätsdaten aus mdn/browser-compat-data, auf Chrome for Developers mit dem Beitrag zum Ursprungsversuch (21.03.2024) und der Abkündigung von unload (Stand 14.07.2026), auf die Chromium Docs zur fetchLater API sowie auf den Web Almanac 2025 des HTTP Archive (Kapitel Performance, 15.01.2026) und web.dev zur Interaction to Next Paint (02.09.2025).

Verwandte Artikel