From 351a6f358cd5e7b8dadc331446a15ea63c494704 Mon Sep 17 00:00:00 2001 From: Pedro Paulo Vezza Campos Date: Fri, 29 May 2026 10:04:36 -0700 Subject: [PATCH] Send client telemetry via sendBeacon to drop it off the critical path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deferral in #28 cut telemetry JS but left the App Insights /v2.1/track POST on the critical request chain (max critical path latency ~2.35s). Lighthouse classifies a request as critical mainly by priority, and a plain fetch() defaults to High — so delaying *when* it fires only moved the node later in the timeline, it didn't remove it. Switch the transport to navigator.sendBeacon, a background-priority transport Lighthouse does not count as critical, with a low-priority keepalive fetch fallback. Beacon also reliably survives page unload, matching the existing visibilitychange/pagehide flush. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/telemetry.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib/telemetry.ts b/src/lib/telemetry.ts index a989075..f455aac 100644 --- a/src/lib/telemetry.ts +++ b/src/lib/telemetry.ts @@ -76,12 +76,24 @@ function send(envelopes: Envelope[]): void { if (!config || envelopes.length === 0) return; const url = `${config.ingestionEndpoint}/v2.1/track`; const payload = envelopes.map((e) => JSON.stringify(e)).join("\n"); + // sendBeacon is a background-priority transport, so the ingestion POST stays + // off the page's critical request chain. A plain fetch defaults to High + // priority, which Lighthouse counts as critical (this slow westus2 round-trip + // was dominating the chain). Beacon also reliably survives page unload, which + // is exactly what the visibilitychange/pagehide flush relies on. + if (typeof navigator.sendBeacon === "function") { + const blob = new Blob([payload], { type: "text/plain" }); + if (navigator.sendBeacon(url, blob)) return; + } + // Fallback for the rare browser without sendBeacon: an explicitly + // low-priority keepalive fetch so the request still stays off the chain. fetch(url, { method: "POST", body: payload, headers: { "content-type": "text/plain" }, keepalive: true, credentials: "omit", + priority: "low", }).catch(() => {}); }