Glossar

Warum sendBeacon vs fetch keepalive eine 64-KiB-Entscheidung ist

Taras Shynkarenko
Taras Shynkarenko
•Aktualisiert: •8 Min. Lesezeit
Warum sendBeacon vs fetch keepalive eine 64-KiB-Entscheidung istWarum sendBeacon vs fetch keepalive eine 64-KiB-Entscheidung ist

TL;DR, Kurzantwort

8 Min. Lesezeit

Der Abstand zwischen sendBeacon vs fetch keepalive liegt in der Kontrolle, nicht in der Zustellung. navigator.sendBeacon() stellt einen POST in die Warteschlange, der die Seite überlebt, und liefert nur true oder false zurück, mit einem Content-Type, den der Browser aus dem übergebenen Body-Typ ableitet. fetch() mit keepalive auf true ergänzt eigene Header, andere Methoden und die Antwort, und beide zehren vom selben Budget von 64 Kibibyte für laufende Anfragen, das im WHATWG-Fetch-Standard definiert ist.

Was ist der Unterschied zwischen sendBeacon vs fetch keepalive?

Der praktische Unterschied zwischen sendBeacon vs fetch keepalive ist Kontrolle: navigator.sendBeacon() stellt einen POST in die Warteschlange, der die Seite überlebt, gibt aber nichts zurück außer true oder false, während fetch() mit keepalive: true Sie die Methode wählen, eigene Header setzen und die Serverantwort lesen lässt. Die W3C-Beacon-Spezifikation ist eindeutig: "The sendBeacon() method does not provide ability to customize the request method, provide custom request headers, or change other processing properties of the request and response. Applications that require non-default settings for such requests should use the [FETCH] API with keepalive set to true." Beide laufen darunter über dieselbe Fetch-Maschinerie, also gilt für beide dieselbe Größenbeschränkung.

Wie viele Daten können sendBeacon vs fetch keepalive senden?

Eine keepalive-Anfrage trägt höchstens 64 Kibibyte Body, also 65.536 Bytes. Der WHATWG-Fetch-Standard erzwingt die Obergrenze innerhalb von HTTP-network-or-cache fetch, bevor die Anfrage das Netzwerk erreicht:

If contentLength is non-null and httpRequest's keepalive is true, then:

  1. Let inflightKeepaliveBytes be 0.
  2. Let group be httpRequest's client's fetch group.
  3. Let inflightRecords be the set of fetch records in group whose request's keepalive is true and done flag is unset.
  4. For each fetchRecord of inflightRecords:
    1. Let inflightRequest be fetchRecord's request.
    2. Increment inflightKeepaliveBytes by inflightRequest's body's length.
  5. If the sum of contentLength and inflightKeepaliveBytes is greater than 64 kibibytes, then return a network error.

Die Anmerkung der Spezifikation erklärt die Zahl: "The above limit ensures that requests that are allowed to outlive the environment settings object and contain a body, have a bounded size and are not allowed to stay alive indefinitely." Messen Sie die serialisierte Nutzlast in Bytes, nicht in Zeichen, bevor Sie darauf vertrauen, dass sie hineinpasst.

Wird die 64-KiB-Grenze über alle laufenden keepalive-Anfragen geteilt?

Die Grenze gilt geteilt, nicht pro Aufruf. Schritt 5 oben addiert contentLength und inflightKeepaliveBytes, die gesamte Body-Länge jeder unbeendeten keepalive-Anfrage in der Fetch-Gruppe. Die Beacon-Spezifikation sagt dasselbe von ihrer Seite: "Requests initiated via the Beacon API automatically set the keepalive flag, and developers can similarly set the same flag manually when using the Fetch API. All requests with this flag set share the same in-flight quota restrictions that is enforced within the Fetch API." Ein sendBeacon()-Aufruf und ein fetch(..., { keepalive: true })-Aufruf konkurrieren um ein Budget.

Der Spielraum für Ihren nächsten Aufruf ist:

remaining bytes = 65536 - (sum of body lengths of unfinished keepalive requests)

Angenommen, ein Fehlerbericht mit 12.288 Bytes und ein Klick-Batch mit 8.192 Bytes sind beide unterwegs. Der Spielraum beträgt 65.536 minus 20.480, also 45.056 Bytes. Eine Sitzungszusammenfassung mit 50.000 Bytes treibt die Summe auf 70.480, also gibt fetch() einen Netzwerkfehler zurück und sendBeacon() liefert false. Bündeln Sie jede Nutzlast aus dem Event-Tracking in einen einzigen Flush, dann hört diese Rechnung auf zu beißen.

Wo das 64-KiB-Budget hingeht
Fehlerbericht12.288 Bytes
Klick-Batch8.192 Bytes
Beide gleichzeitig20.480 Bytes
Verbleibender Spielraum45.056 Bytes
Volle Obergrenze65.536 Bytes
Jede unfertige Keepalive-Anfrage in der Fetch-Gruppe zehrt vom selben 65.536-Byte-Limit, sodass der Spielraum das ist, was nach den bereits laufenden Anfragen übrig bleibt.

Welchen Content-Type setzt sendBeacon für welchen Body-Typ?

sendBeacon lässt Sie Content-Type nie selbst setzen: Der Browser leitet ihn aus dem übergebenen Body-Typ ab, und dieser Wert entscheidet, ob die Anfrage im Modus no-cors bleibt oder einen CORS-Preflight auslöst. Das Verarbeitungsmodell der Beacon-Spezifikation beschreibt die Verzweigung:

If contentType is not null:

  1. Set corsMode to "cors".
  2. If contentType value is a CORS-safelisted request-header value for the Content-Type header, set corsMode to "no-cors".
  3. Append a Content-Type header with value contentType to headerList.

Der Fetch-Standard definiert den Safelist-Test für diesen Header: "If mimeType's essence is not application/x-www-form-urlencoded, multipart/form-data, or text/plain, then return false." Fetch legt außerdem fest, welchen Content-Type jeder Body-Typ erzeugt:

Body, den Sie an sendBeacon übergebenContent-Type, den der Browser setztAuf der CORS-SafelistCross-Origin-Ergebnis
ArrayBuffer, TypedArray, DataViewkeinerkein Header gesendetno-cors, kein Preflight
Stringtext/plain;charset=UTF-8Jano-cors, kein Preflight
URLSearchParamsapplication/x-www-form-urlencoded;charset=UTF-8Jano-cors, kein Preflight
FormDatamultipart/form-data; boundary=...Jano-cors, kein Preflight
Blob mit dem Typ application/jsonapplication/jsonNeinCORS-Modus, Preflight nötig

Die Beacon-Spezifikation nennt die Konsequenz: "If the request does not contain a payload, or the request Content-Type is a CORS-safelisted request-header, then the request mode is no-cors." Andernfalls wird, in den Worten der Spezifikation, "a CORS preflight is made and the server needs to first allow such requests by returning the appropriate set of CORS headers: Access-Control-Allow-Credentials, Access-Control-Allow-Origin, Access-Control-Allow-Headers." Ein Preflight verdoppelt die Roundtrips auf einer Seite, die ohnehin schon entladen wird, senden Sie JSON also als einfachen String und parsen Sie es serverseitig.

Eine Person klappt einen Laptop-Deckel zu, der Moment, in dem ein Browser-Tab schließt und ein Beacon feuern muss.

Warum sollte ein Beacon bei pagehide statt bei unload feuern?

Feuern Sie das Beacon zuerst bei visibilitychange und als Zweites bei pagehide, und lassen Sie unload in Ruhe, denn unload zerstört den Back/Forward-Cache. Die sendBeacon-Seite von MDN sagt es unmissverständlich: "the unload event is incompatible with the back/forward cache (bfcache) implemented in modern browsers. Some browsers, such as Firefox, handle this incompatibility by excluding pages from the bfcache if they contain unload handlers, thus hurting performance." MDN empfiehlt pagehide als Fallback für Browser ohne visibilitychange: "Like beforeunload and unload, this event is not reliably fired, especially on mobile. However, it is compatible with the bfcache."

Die Beacon-Spezifikation ergänzt den mobilen Grund: "Developers should avoid relying on unload event because it will not fire whenever a page is in a background state (i.e. visibilityState equal to hidden) and the process is terminated by the mobile OS." Die vollständige Abwägung zwischen den Exit-Events steht in beforeunload vs pagehide.

Flowsery
Flowsery

Jetzt 14 Tage kostenlos testen

Echtzeit-Dashboard

Zielverfolgung

Cookie-freies Tracking

Wie schreiben Sie einen sendBeacon-Aufruf, der das Entladen überlebt?

Übergeben Sie einen einfachen String, prüfen Sie den booleschen Rückgabewert, und leeren Sie den Puffer erst, wenn der Browser die Warteschlange annimmt:

const endpoint = "https://collect.example.com/session";
const buffer = { sessionId: crypto.randomUUID(), events: [] };
 
function flush() {
  if (buffer.events.length === 0) return;
  const body = JSON.stringify(buffer);
  if (navigator.sendBeacon(endpoint, body)) {
    buffer.events.length = 0;
  }
}
 
document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "hidden") flush();
});
 
window.addEventListener("pagehide", flush);

Der String-Body erzeugt text/plain;charset=UTF-8, was auf der Safelist steht, also überspringt der Cross-Origin-POST den Preflight. Ein false als Rückgabewert bedeutet, dass die Nutzlast plus alles Laufende die 65.536 Bytes überschritten hat, und der Puffer bleibt für den nächsten Versuch erhalten.

Wie schreiben Sie denselben Aufruf mit fetch keepalive?

Setzen Sie keepalive: true, fügen Sie die Header hinzu, die sendBeacon nicht mitträgt, und akzeptieren Sie, dass das Promise nur auflöst, wenn die Seite lange genug lebt:

window.addEventListener("pagehide", () => {
  fetch("https://collect.example.com/session", {
    method: "POST",
    keepalive: true,
    headers: {
      "Content-Type": "application/json",
      "X-Api-Key": apiKey,
    },
    body: JSON.stringify(buffer),
  }).catch(() => {});
});

Sowohl Content-Type: application/json als auch der eigene X-Api-Key-Header liegen außerhalb der CORS-Safelist, ein Cross-Origin-Aufruf löst hier also einen OPTIONS-Preflight aus und braucht Access-Control-Allow-Headers auf dem Server. Eine Einschränkung gilt nur für keepalive: Der Extract-a-body-Algorithmus des Fetch-Standards wirft bei gesetztem Flag einen TypeError für einen ReadableStream-Body, Streaming beim Verlassen fällt also aus. Genau dieser Moment des Verlassens ist der Grund, warum sich ein schlankes Skript auszahlt, und warum sich der Performance-Overhead von Session Replay beim Flush zeigt und nicht beim Aufzeichnen.

Welches sollten Sie verwenden?

Nutzen Sie sendBeacon für Fire-and-Forget-Analytics und fetch keepalive, sobald ein Header, eine Methode oder die Antwort zählt.

AnforderungsendBeaconfetch keepalive
HTTP-Methodenur POSTjede Methode
Eigene Request-Headernicht unterstütztunterstützt
Kontrolle über Content-Typeaus dem Body-Typ abgeleitetvon Ihnen gesetzt
Serverantwortnicht verfügbarüber das Promise lesbar
Body-Größeteilt das 64-KiB-Budgetteilt dasselbe Budget
Verfügbar in Service WorkernNeinJa

Jenseits von 64 Kibibyte funktioniert keines von beiden: Sampeln Sie die Nutzlast, teilen Sie sie auf, oder verlagern Sie die Aggregation auf den Server, eine Wahl, die clientseitige und serverseitige Analytics behandelt.

Netzwerkkabel in einem Serverrack, sie stehen für den Endpunkt, der Keepalive-Anfragen empfängt.

Häufig gestellte Fragen

Bedeutet ein Rückgabewert true bei sendBeacon, dass der Server die Daten erhalten hat?

Nein. Die Beacon-Spezifikation sagt, ein true als Rückgabewert "implies the browser has queued the data for transfer" und dass "since the actual data transfer happens asynchronously, this method does not provide any information whether the data transfer has succeeded or not." Ein false ist das einzige andere Signal, und es bedeutet, dass die Warteschlange die Nutzlast abgelehnt hat.

Kann ich bei sendBeacon einen Authorization-Header setzen?

Nein. Die Beacon-Spezifikation stellt fest, dass sendBeacon "does not provide ability to customize the request method, provide custom request headers, or change other processing properties of the request and response." Verlagern Sie die Zugangsdaten in den URL-Pfad, in ein Cookie oder in den Body, oder wechseln Sie zu fetch() mit keepalive: true.

Warum löst mein sendBeacon-Blob einen CORS-Preflight aus?

Weil der type des Blob zum Content-Type der Anfrage wird und application/json nicht auf der CORS-Safelist steht. Der Fetch-Standard beschränkt diese Safelist auf application/x-www-form-urlencoded, multipart/form-data und text/plain, alles andere erzwingt also einen OPTIONS-Preflight. Senden Sie das JSON als String, dann bleibt die Anfrage im Modus no-cors.

Gilt das 64-KiB-Limit pro Anfrage oder pro Seite?

Pro Fetch-Gruppe, über jede unbeendete keepalive-Anfrage darin. Der Fetch-Standard addiert die contentLength des neuen Body zu inflightKeepaliveBytes und gibt jenseits von 64 Kibibyte einen Netzwerkfehler zurück, ein großes Beacon, das noch unterwegs ist, verkleinert also den Platz für das nächste.

Funktioniert fetch keepalive in einem Service Worker?

Ja. MDN führt die Verfügbarkeit im Service Worker als Vorteil von fetch() mit keepalive gegenüber sendBeacon() auf, neben Nicht-POST-Methoden, eigenen Request-Eigenschaften und dem Zugriff auf die Antwort.

Was passiert mit einer keepalive-Anfrage, wenn der Nutzer den Tab sofort schließt?

Der Browser hält die Anfrage über das Dokument hinaus am Leben, das sie gestartet hat, genau dafür ist das Flag da. Der Fetch-Standard beschreibt keepalive so, dass es "the request to outlive the environment settings object" erlaubt, und die Obergrenze existiert, damit diese überlebenden Anfragen "have a bounded size and are not allowed to stay alive indefinitely." Was Sie verlieren, ist die Antwort: Das Promise hat keine lebende Seite, in die es auflösen könnte.

Kann sendBeacon andere Methoden als POST verwenden?

sendBeacon sendet ausschließlich POST, wie die Vergleichstabelle zeigt. fetch mit keepalive akzeptiert jede Methode, wechseln Sie also zu fetch, wenn der Endpunkt PUT, PATCH oder GET erwartet.

Warum schlägt ein Stream-Body bei fetch keepalive fehl?

Der Extract-a-Body-Algorithmus des Fetch-Standards wirft einen TypeError für einen ReadableStream-Body, sobald keepalive gesetzt ist, sodass ein Stream die Seite beim Verlassen nicht verlassen kann. Serialisieren Sie die Nutzlast mit JSON.stringify oder übergeben Sie stattdessen einen String-Body.

Flowsery
Flowsery

Jetzt 14 Tage kostenlos testen

Echtzeit-Dashboard

Zielverfolgung

Cookie-freies Tracking

Sollte ich pagehide und visibilitychange zusammen abhören oder mich für eines entscheiden?

Feuern Sie zuerst bei visibilitychange, da dieses Ereignis erfasst, wenn ein Tab in den Hintergrund wechselt, bevor die Seite tatsächlich entladen wird, und fügen Sie pagehide als Fallback für Browser hinzu, in denen visibilitychange nicht zuverlässig feuert. Die Flush-Funktion im Beispiel des Beitrags registriert beide Listener und lässt den zuerst feuernden Beacon senden.

Was tun, wenn meine Nutzlast über 64 KiB wächst?

Weder sendBeacon noch fetch keepalive können einen Body über 65.536 Bytes hinaus transportieren; die Anfrage liefert einen Netzwerkfehler oder ein false-Ergebnis, sobald das gemeinsame Budget überschritten ist. Kürzen Sie die Nutzlast per Stichprobe, teilen Sie sie auf mehrere Flushes auf oder verlagern Sie die Aggregation auf den Server.

War dieser Artikel hilfreich?

Teilen Sie uns Ihre Meinung mit!

Sieh uns öfter bei Google

Ein Klick macht Flowsery zu einer bevorzugten Quelle. Unsere Artikel stehen dann weiter oben in deinen Top-Meldungen, im KI-Modus und in den KI-Übersichten.

Bevor Sie gehen...

Flowsery

Flowsery

Umsatzorientierte Analysen für Ihre Website

Verfolgen Sie jeden Besucher, jede Quelle und jede Conversion in Echtzeit. Einfach, leistungsstark und komplett ohne Cookies.

Echtzeit-Dashboard

Zielverfolgung

Cookie-freies Tracking

Verwandte Glossarbegriffe

Warum Beforeunload vs Pagehide entscheidet, ob Analytics-Daten überlebenWarum Beforeunload vs Pagehide entscheidet, ob Analytics-Daten überleben
Glossar

Warum Beforeunload vs Pagehide entscheidet, ob Analytics-Daten überleben

Im Vergleich zeigt Beforeunload vs pagehide, warum Mobilgeräte beforeunload überspringen, den Bfcache blockieren, und pagehide mit sendBeacon Daten sendet.

•6 Min. Lesezeit
Warum CNAME-Cloaking Tracker nicht mehr vor Safari oder Brave verstecktWarum CNAME-Cloaking Tracker nicht mehr vor Safari oder Brave versteckt
Glossar

Warum CNAME-Cloaking Tracker nicht mehr vor Safari oder Brave versteckt

Tracker tarnten sich per CNAME-Cloaking als Ihre Subdomain. Wie der Trick wirkt, warum Safari Cookies auf 7 Tage kappt und wie Brave und uBlock ihn enttarnen.

•9 Min. Lesezeit
Wie Cookie-Syncing Werbe-IDs über Websites hinweg abgleichtWie Cookie-Syncing Werbe-IDs über Websites hinweg abgleicht
Glossar

Wie Cookie-Syncing Werbe-IDs über Websites hinweg abgleicht

Ad-Tech-Firmen tauschen per Cookie-Syncing Nutzer-IDs über Redirects und Pixel. Was Safari, Firefox und Chrome dagegen tun und warum Analytics es nie braucht.

•9 Min. Lesezeit
Wie Sie berechnete Messwerte in GA4 mit nur fünf Plätzen erstellenWie Sie berechnete Messwerte in GA4 mit nur fünf Plätzen erstellen
Glossar

Wie Sie berechnete Messwerte in GA4 mit nur fünf Plätzen erstellen

Google erlaubt nur 5 berechnete Messwerte in GA4 pro Standard-Property. Hier stehen Formelsyntax, Einheiten, Fundorte und was keine Formel nutzen darf.

•8 Min. Lesezeit
So richten Sie die Content-Gruppierung in GA4 mit einem Parameter einSo richten Sie die Content-Gruppierung in GA4 mit einem Parameter ein
Glossar

So richten Sie die Content-Gruppierung in GA4 mit einem Parameter ein

So richten Sie die Content-Gruppierung in GA4 mit content_group per gtag oder Tag Manager ein, lesen die Dimension Content group und vermeiden (not set).

•8 Min. Lesezeit
Was User-Agent Client Hints senden und was Analytics noch siehtWas User-Agent Client Hints senden und was Analytics noch sieht
Glossar

Was User-Agent Client Hints senden und was Analytics noch sieht

Chrome teilt Browserdaten über User-Agent Client Hints in Sec-CH-UA-Header auf. Low vs. High Entropy, Accept-CH, der reduzierte UA und was Analytics liest.

•9 Min. Lesezeit

Verwandte Artikel