Performance

RUM in casa, senza servizi: cosa serve davvero

Un beacon RUM pesa 239 byte e un lotto di venti campioni scende da 4.807 a 273 byte con gzip. Bastano PerformanceObserver e sendBeacon, senza servizi esterni.

In sintesi

  • Un campione RUM in JSON pesa 239 byte: venti campioni in un lotto arrivano a 4.807 byte.
  • Con gzip lo stesso lotto scende a 273 byte, 17,6 volte meno: il costo di rete è trascurabile.
  • PerformanceObserver più sendBeacon su pagehide bastano: nessun servizio esterno, nessuna dipendenza.
  • Il numero da pubblicare è il 75° percentile, non la media: la media nasconde la coda lenta.

Un RUM in casa si costruisce con due API del browser, PerformanceObserver e navigator.sendBeacon, e un endpoint che riceve JSON. Il costo di rete non è il problema: un campione pesa 239 byte e venti campioni in un lotto compresso stanno in 273 byte.

Cosa raccogliere, e quando

MetricaAPIMomentoCosa risponde
TTFBnavigation.responseStartpagehideil server quanto ci mette
LCPPerformanceObserver largest-contentful-paintpagehidequando appare il contenuto principale
CLSPerformanceObserver layout-shiftpagehidequanto si muove la pagina
INPPerformanceObserver event, interactionIdpagehidequanto risponde agli input

Il collector minimo

const q = [];
const push = (kind, value) => q.push({ kind, value, path: location.pathname });
new PerformanceObserver((l) => {
  for (const e of l.getEntries()) push("lcp", Math.round(e.startTime));
}).observe({ type: "largest-contentful-paint", buffered: true });
new PerformanceObserver((l) => {
  for (const e of l.getEntries()) if (!e.hadRecentInput) push("cls", +e.value.toFixed(4));
}).observe({ type: "layout-shift", buffered: true });
addEventListener("pagehide", () => {
  const nav = performance.getEntriesByType("navigation")[0];
  push("ttfb", Math.round(nav.responseStart));
  navigator.sendBeacon("/rum", new Blob([JSON.stringify(q)], { type: "application/json" }));
});

Il codice richiede un endpoint /rum che accetti POST JSON e un browser con sendBeacon. Il vantaggio di sendBeacon è che la richiesta sopravvive alla chiusura della pagina, dove fetch verrebbe annullata. L'evento pagehide copre anche il passaggio in bfcache, dove unload non arriva.

Quanto pesa davvero

node -e "const z=require('zlib');const b=JSON.stringify(Array.from({length:20},(_,i)=>({kind:'lcp',value:1200+i*20})));console.log(Buffer.byteLength(b),z.gzipSync(b).length)"

Il comando richiede Node e nessuna dipendenza. Su un lotto reale i valori cambiano con il numero di campioni, ma l'ordine di grandezza resta: pochi kilobyte per visita, ridotti a poche centinaia di byte quando il server comprime la richiesta. Se il tuo endpoint non comprime, aggiungi Content-Encoding: gzip e accetta il corpo compresso: il risparmio è di un fattore 17.

Dal campione al numero pubblicabile

Un RUM che pubblica la media mente: la media nasconde la coda lenta. Si ordina il campione per metrica e si prende il valore al 75° percentile, che è il numero usato da CrUX e da Search Console. Con venti campioni il 75° percentile è il quindicesimo valore in ordine crescente, un dato troppo rumoroso per una decisione: serve un volume di visite sufficiente, altrimenti si legge il dato come indicazione e non come verdetto.

Il limite da dichiarare

Chi blocca gli script non invia nulla, quindi il campione è distorto verso chi ha un browser pulito e una connessione decente. Le metriche perse per crash o chiusura brusca non arrivano. E un beacon che contiene il path è un dato personale: serve una base giuridica, la minimizzazione dei campi e una retention breve, non un foglio di calcolo eterno.

Dove continuare

Il catalogo EneUI indicizza 111.985 componenti, il blocco banner 01 e l'icona chart bar, quella che finisce nel pannello. Per la meccanica degli osservatori leggi PerformanceObserver in pratica, per il confronto tra laboratorio e campo misurare l'LCP, per le soglie cosa sono i Core Web Vitals, e per il confronto tra architetture framework frontend a confronto.

Domande frequenti

Serve un servizio esterno per il RUM?

No. PerformanceObserver raccoglie le metriche nel browser e sendBeacon le invia a un endpoint tuo. Un servizio esterno aggiunge dashboard e confronti, che puoi ricostruire con una query.

Perché inviare su pagehide e non su unload?

Perché unload non arriva quando la pagina entra in bfcache, e su mobile molti browser lo saltano. pagehide copre entrambi i casi e sendBeacon sopravvive alla chiusura.

Quanti campioni servono per un 75° percentile affidabile?

Almeno qualche centinaio di visite per pagina e per tipo di dispositivo. Con venti campioni il percentile balla di centinaia di millisecondi e non regge una decisione.

Il beacon RUM rallenta la pagina?

No, se parte su pagehide e resta sotto il kilobyte compresso. Il costo vero è lato server: scrivere una riga per visita e aggregare i percentili con una query pianificata.

Come tratto i dati personali nel beacon?

Raccogli solo ciò che serve: path, tipo di dispositivo, metriche. Niente identificatori stabili, retention breve e informativa nell'informativa privacy. Il path può identificare una persona su pagine rare.