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
| Metrica | API | Momento | Cosa risponde |
|---|---|---|---|
| TTFB | navigation.responseStart | pagehide | il server quanto ci mette |
| LCP | PerformanceObserver largest-contentful-paint | pagehide | quando appare il contenuto principale |
| CLS | PerformanceObserver layout-shift | pagehide | quanto si muove la pagina |
| INP | PerformanceObserver event, interactionId | pagehide | quanto 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.