Home›Journal›This post

fetchLater vs sendBeacon for Exit Analytics

Compare lifecycle timing, request controls, UTF-8 quota, feature fallbacks, and server reconciliation without issuing a request.

JP
JP Casabianca
AI Engineer and Product Designer · full-stack delivery · Bogotá

fetchLater vs sendBeacon is a page-lifecycle decision, not a guarantee that analytics reached your database. This comparison maps timing, request controls, quota, feature detection, and server reconciliation into a tested exit-analytics policy.

Define the fetchLater vs sendBeacon outcome

The product outcome is not “call an API during unload.” It is “record an important event with known semantics, bounded cost, graceful fallback, and server-side evidence.” That framing immediately separates browser transport state from business-processing state.

For each event, define urgency, acceptable delay, body bytes, HTTP method, required headers, whether a response must be read, duplication tolerance, and reconciliation need. A low-value page-exit analytics pulse may accept delayed best effort. A billing or entitlement transition should not depend on either browser exit mechanism as its system of record.

The WHATWG deferred-fetch specification defines fetchLater and its lifecycle. It gives the browser responsibility for activating a deferred request under specified conditions. It does not transform activation into a database commit. The lab therefore uses separate states for registered, activated, network-attempted, accepted, deduplicated, processed, and reconciled.

Start from the server receipt you want, then work backward to transport. Include an event ID, schema version, occurred-at time, anonymous or authorized subject boundary, and an idempotency key. Keep the payload compact. The same event-delivery vocabulary is developed in delivery semantics foundations; the browser lifecycle adds another uncertain hop, not an exception to distributed-systems reasoning.

With that outcome defined, fetchLater vs sendBeacon becomes a policy choice rather than a reliability slogan.

Registration, activation, and server work are different statesSix lifecycle checkpoints cross page, user-agent, network, and server lanes without turning a client signal into a processing receipt.PAGE-EXIT LIFECYCLE · OBSERVABILITY STAYS IN ITS LANEPAGEUSER AGENTNETWORKSERVERREGISTERHIDDENACTIVATEATTEMPTACCEPTRECONCILEvisibilitychange / BFCache boundary
Registration, activation, and server work are different states. Six lifecycle checkpoints cross page, user-agent, network, and server lanes without turning a client signal into a processing receipt.
Who can prove each state?
StateObserverDoes it prove processing?
Queued or registeredPage / user agentNo
ActivatedUser agentNo
Network attemptedUser agent / networkNo
AcceptedIngestion serviceNot downstream processing
Deduplicated and processedDurable server systemsYes, for their own stage
ReconciledProduct data systemEnd-to-end evidence

Reading rule: labels, order, values, shapes, and patterns carry the conclusion; color is supplementary.

Map registration, hiding, activation, and reconciliation

A useful fetchLater vs sendBeacon lifecycle map has lanes for the page, user agent, network, ingestion service, deduplication store, and analytics warehouse. The page registers or queues work while it is alive. A visibility transition may trigger an explicit fallback. The user agent may activate deferred work on timeout or termination. Network and server stages happen later and can fail independently.

Do not center the design on unload. Modern page lifecycles include backgrounding, process termination, freezes, and back-forward cache navigation. The HTML page visibility section provides the stable signal used by a visibilitychange fallback, but hidden does not always mean final exit. That is why event IDs and idempotent ingestion matter.

BFCache deserves its own lane. A page can become hidden, be preserved, and later return. If the client sends the same logical event twice, the backend should distinguish a retry from a new event. If the event is a session-end guess, model it as a provisional observation that can be amended rather than as unquestionable truth.

The figure marks registration, activation, and server acknowledgement with different shapes and line styles. The visible semantic table names which actor can observe each state. That non-color encoding prevents a common conceptual collapse: a browser promise or Boolean belongs to the client lane, while acceptance and processing belong to server lanes.

Read request and response semantics literally

The fetchLater vs sendBeacon distinction begins with literal API semantics. sendBeacon accepts a data payload for an HTTP POST chosen by the browser and returns a Boolean. A true result means the user agent queued the data for transfer. It is not a response status, delivery confirmation, deduplication result, or processing receipt. The Beacon specification is explicit about asynchronous, non-blocking delivery intent.

fetchLater uses the Fetch model, so it can represent more request controls, including method, headers, and body subject to its rules. Its activated promise tells the page that the deferred request has been activated. That signal still does not expose the eventual response body or prove server processing. The page may no longer exist when network work occurs.

Choose from required semantics. If the client must read a response to proceed, neither exit-only pattern satisfies that requirement; send a normal fetch earlier while the page is active. If custom method or headers are essential and deferred fetch is available within quota, fetchLater may fit. If a small POST with simple semantics is enough, Beacon API support can be a practical fallback.

The lab encodes those branches as policy reasons, not as a browser compatibility snapshot. It never executes either API. Feature support is an input, so reviewers can test states deterministically without causing traffic. The receipt always says what a positive client signal means and, equally important, what it does not mean.

Budget deferred fetch quotas in UTF-8 bytes

A fetchLater vs sendBeacon policy must account for bounded browser capacity before registration and retain headroom for other requests in the same relevant context. Character count is insufficient because UTF-8 payloads can use more than one byte per code point, and headers plus request metadata also consume budget.

The local model computes body bytes with TextEncoder, adds the declared header names and values in UTF-8, and adds a clearly labeled request-overhead estimate. It then compares the result with the specification quota inputs carried in the receipt. Those constants are named and versioned so a future standards change becomes a visible diff.

QUOTA_IGNORED is a required mutant. It recommends deferred fetch even when the estimated request exceeds the available budget. The oracle must reject it with a quota-specific code rather than allowing a later generic fallback. The result does not claim that a real browser would use the exact estimate; it proves that the policy considers its declared budget conservatively.

Payload discipline is valuable on every path. Prefer a compact event envelope to a snapshot of application state. Cap body text before serialization, reject non-finite delay values, and constrain method and header selections to a short allowlist. If an event cannot fit within the bounded page-exit analytics envelope, move it to an earlier, acknowledged workflow instead of hiding the risk in an exit callback.

Transport controls choose a path, not an outcomeA matrix compares normal fetch, deferred fetch, and Beacon across timing, request controls, response access, quota, and support gates.EXIT-ANALYTICS CAPABILITY MATRIXCONSTRAINTNORMAL FETCHfetchLatersendBeaconTimingwhile activedeferred activationsmall queued POSTMethod / headersFetch controlsFetch controls + rulesbrowser-selected POSTRead responseyesno exit receiptnoBudgetnormal limitsdeferred quotapayload / UA limitSupport gatefetch presentfeature detectfeature detectMeaningresponse observedactivated onlyqueued onlyCritical acknowledged work moves earlier; exit transports remain best-effort.
Transport controls choose a path, not an outcome. A matrix compares normal fetch, deferred fetch, and Beacon across timing, request controls, response access, quota, and support gates.
Policy comparison
NeedPreferred action
Read response or critical operationNormal fetch while the page is active
Deferred request controls, quota, and support availablefetchLater candidate
Small best-effort POSTsendBeacon candidate
No compatible featureRetain for next active session or accept documented loss

Reading rule: labels, order, values, shapes, and patterns carry the conclusion; color is supplementary.

Build a feature-detected visibilitychange fallback

A robust fetchLater vs sendBeacon policy is ordered. First, send critical acknowledged work before the exit window. For eligible best-effort events, choose fetchLater only when the feature is present, request controls are compatible, quota is available, and no response is required. Otherwise choose a small sendBeacon payload when its semantics fit. If neither feature is available, retain the event for the next active session or accept the documented loss policy.

Feature detection must happen at the call site. NO_FEATURE_FALLBACK is a mutant that chooses deferred fetch while support is false. The oracle expects a named fallback decision. A support table copied into an article becomes stale; a runtime gate plus a revisit trigger remains truthful.

Use visibilitychange for an explicit fallback signal, but do not depend on unload or beforeunload. UNLOAD_DEPENDENCY marks any policy that requires those events as invalid. The lab includes hidden, timeout, termination, BFCache, and still-visible scenarios so one happy lifecycle cannot stand in for the product's full state space.

Fallback does not mean duplicate implementation logic. One policy function produces a transport recommendation and an event envelope; thin adapters execute it in production. The local lab exercises only the pure policy function. The same separation improves idempotency-key lifecycle contracts: client retries and server identity rules can evolve independently while sharing one receipt vocabulary.

Make ingestion idempotent and reconcilable

Every fetchLater vs sendBeacon transport path can duplicate, reorder, delay, or lose events. The ingestion endpoint should validate schema, authenticate within the chosen privacy boundary, store a stable event ID, and return an acceptance status when a response channel exists. A unique constraint or equivalent idempotency store prevents the same logical event from being counted twice.

Do not use a client-generated “delivered” flag as evidence. Server acceptance belongs in server logs. Deduplication belongs in durable state. Processing belongs in a downstream job or transaction. Reconciliation compares expected product facts with accepted and processed events. Webhook reconciliation views demonstrate the same architectural pattern for server-to-server traffic.

NO_IDEMPOTENCY is a required mutant. When the event is marked durable or retries are possible but the backend-control input lacks an idempotency key, the policy must reject the design. That gate is intentionally independent of transport. Adding fetchLater does not make an ingestion endpoint safer, and using Beacon does not excuse duplicate counting.

For analytics, reconcile aggregate gaps rather than pretending each browser exit is guaranteed. Track queued or registered client counters separately from accepted server counters when privacy and architecture allow. A meaningful alert describes the boundary: registration rose while acceptance fell, duplicates increased, or processing lag grew. It never equates fetchLater activation or sendBeacon true with business completion.

Client signals stop before server truthThe state machine uses distinct shapes for client observations, uncertain transitions, durable server evidence, and six mutation claims that must be rejected.END-TO-END STATE MACHINE · NO SEMANTIC TELEPORTATIONQUEUEDclientACTIVATEDclientNETWORKuncertainACCEPTEDserverDEDUPEDdurablePROCESSEDdurableREJECTED CLAIMSQUEUE_TRUE_IS_DELIVEREDACTIVATED_IS_PROCESSEDUNLOAD_DEPENDENCYQUOTA_IGNOREDNO_FEATURE_FALLBACKNO_IDEMPOTENCYOnly server-owned evidence advances accepted → deduplicated → processed → reconciled.
Client signals stop before server truth. The state machine uses distinct shapes for client observations, uncertain transitions, durable server evidence, and six mutation claims that must be rejected.
  1. Queued: sendBeacon returned true; delivery is not proven.
  2. Activated: deferred work activated; processing is not proven.
  3. Network: an attempt may still fail or be rejected.
  4. Accepted: server validated the envelope.
  5. Deduplicated: durable event identity prevented double count.
  6. Processed and reconciled: downstream evidence completed the product outcome.

Reading rule: labels, order, values, shapes, and patterns carry the conclusion; color is supplementary.

Test the exit policy without issuing a request

The fetchLater vs sendBeacon decision lab is deliberately local-only. Inputs cover method, allowed headers, bounded body text, lifecycle state, activation delay, feature support, response need, durability, idempotency, and reconciliation. Pressing Compare builds a deterministic scenario, estimates UTF-8 cost, recommends a policy, and prints reasons. No adapter for fetch, fetchLater, sendBeacon, image pixels, forms, or sockets exists in the document.

An independent oracle rebuilds the decision from the exported inputs. It checks disqualifiers before preferences: response required, unsafe lifecycle dependency, unsupported feature, incompatible request controls, quota exhaustion, and missing backend controls. The policy and oracle use different control flow and must agree on recommendation and reason codes.

Six mutants defend the thesis. QUEUE_TRUE_IS_DELIVERED upgrades Beacon queueing into delivery. ACTIVATED_IS_PROCESSED upgrades deferred activation into server processing. UNLOAD_DEPENDENCY requires a fragile lifecycle event. QUOTA_IGNORED skips the byte gate. NO_FEATURE_FALLBACK ignores support. NO_IDEMPOTENCY permits durable retry without a stable key. Every mutant must fail for its named reason.

JSON exports the scenario, constants, cost breakdown, recommendation, state semantics, oracle result, and mutation ledger. CSV emits one row per check. Both use canonical ordering and exclude timestamps. The UI writes user content with textContent only, constrains all choices to form controls, limits body length, and remains usable by keyboard at narrow widths and 200% zoom.

Use a decision matrix and preserve the boundary

Choose normal fetch while the page is active when the operation is critical or the client must read a response. Choose fetchLater for eligible deferred requests that need Fetch controls, fit the declared quota, pass feature detection, and can tolerate unobserved completion. Choose sendBeacon for small best-effort POST analytics when queueing semantics are sufficient. Choose retain-or-drop policy when neither exit transport is supported.

Every row in that matrix assumes idempotent ingestion and honest state names. “Queued,” “registered,” and “activated” are client observations. “Accepted,” “deduplicated,” “processed,” and “reconciled” require server evidence. This vocabulary should appear in dashboards and incident notes as well as code.

Event naming also matters. Product analytics event names help keep payloads stable while transport evolves. A transport migration should not create a new business event unless meaning changed. Carry schema version and transport policy separately so analysts can measure gaps without fragmenting product semantics.

The lab's recommendation is a decision and quota receipt, not a live compatibility report or empirical delivery-rate benchmark. Real rollout still needs browser support monitoring, production acceptance metrics, privacy review, and failure-budget ownership. By preserving that truth boundary, fetchLater vs sendBeacon remains a precise engineering comparison: it tells you how to choose an exit path without pretending that the path is the destination.

Runnable local artifact — The lab produces a decision and declared-quota receipt only; it is not a browser-compatibility snapshot or an empirical delivery-rate claim.

Plain text1 line
Select a bounded local scenario, estimate UTF-8 request cost, apply response, lifecycle, support, quota, and backend-control gates, compare an independent oracle, kill six semantic mutations, and export canonical JSON or CSV without issuing a request.