mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(monitoring): sample web-vitals at 10% so the beacon stops self-throttling
The web-vitals reporter posted every CLS/LCP/FCP/TTFB/INP measurement on every page-load, six beacons per navigation. In production this saturated the route-side per-IP rate-limit (60/min) inside seconds — the runtime audit captured POST /api/internal/web-vitals 429 for almost every metric after the first navigation, so most samples never reached the wide-event sink. Telemetry was self-throttling. Decide once per mount (memoised) whether the current page-load reports its vitals, sample at 10%, and either send every metric for that load or send none. Per-load all-or-none keeps each session's vitals coherent (an LCP from one navigation stays comparable to the CLS from the same navigation) while cutting traffic by an order of magnitude. The route-side rate-limit stays in place as a backstop. Refs: .planning/round-v1433-audit-runtime.md F19
This commit is contained in:
1 parent
743579ca8f
commit
9c738b601b
1 file changed
+35
-5
@@ -1,24 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useReportWebVitals } from "next/web-vitals";
|
||||
|
||||
/**
|
||||
* v1.4.28 R3d — client-side mount that pipes every Web Vitals
|
||||
* measurement to `/api/internal/web-vitals`.
|
||||
* v1.4.28 R3d — client-side mount that pipes Web Vitals measurements
|
||||
* to `/api/internal/web-vitals`.
|
||||
*
|
||||
* Next 16 ships `useReportWebVitals` as the supported hook for
|
||||
* capturing CLS / FCP / FID / LCP / TTFB / INP from the live
|
||||
* navigation. The hook fires once per metric per page load, so even a
|
||||
* heavy navigation produces at most six beacons.
|
||||
* navigation. The hook fires once per metric per page load, so a
|
||||
* heavy navigation can produce up to six beacons per route change.
|
||||
*
|
||||
* v1.4.33 F19 — the reporter previously posted EVERY measurement on
|
||||
* EVERY page-load. In production the runtime audit captured
|
||||
* `POST /api/internal/web-vitals 429` for almost every metric after
|
||||
* the first navigation: six beacons × N navigations burned through
|
||||
* the per-IP 60/min route-side rate-limit in seconds, so most samples
|
||||
* were dropped before they reached the wide-event sink. Telemetry
|
||||
* was self-throttled.
|
||||
*
|
||||
* Switch to client-side sampling at SAMPLE_RATE: each page-load
|
||||
* decides ONCE (memoised via `useMemo`) whether to report and either
|
||||
* sends every metric for that load or sends none. Per-load all-or-
|
||||
* none keeps a sampled session's vitals coherent (the LCP from one
|
||||
* page must be comparable to the CLS from the same page) while
|
||||
* cutting traffic by an order of magnitude. The route-side rate-limit
|
||||
* stays in place as a backstop against runaway sampling.
|
||||
*
|
||||
* Delivery transport: `navigator.sendBeacon` when available (queues
|
||||
* the POST without blocking the page lifecycle, survives the
|
||||
* `pagehide` event) with a `fetch({ keepalive: true })` fallback.
|
||||
* The route accepts the payload best-effort and returns 200 even on
|
||||
* The route accepts the payload best-effort and returns 204 even on
|
||||
* malformed bodies — beacons must never delay or fail the navigation.
|
||||
*/
|
||||
const SAMPLE_RATE = 0.1;
|
||||
|
||||
export function WebVitalsReporter() {
|
||||
// Decide once per mount whether this page-load reports its vitals.
|
||||
// `useMemo` with an empty dep array keeps the decision stable across
|
||||
// every metric callback inside the same navigation — partial sampling
|
||||
// would mix LCP-without-CLS noise into the wide-event aggregates.
|
||||
// `SSR` evaluates Math.random() to a stable false branch (Node), but
|
||||
// the hook only fires post-hydration so the decision uses the
|
||||
// browser's PRNG.
|
||||
const sampled = useMemo(() => Math.random() < SAMPLE_RATE, []);
|
||||
|
||||
useReportWebVitals((metric) => {
|
||||
if (!sampled) return;
|
||||
|
||||
const body = JSON.stringify({
|
||||
id: metric.id,
|
||||
name: metric.name,
|
||||
|
||||
Reference in new issue
Block a user