Real User Monitoring (RUM) 2026: web-vitals.js, p75 percentilek és CrUX a gyakorlatban
Hogyan állíts be saját RUM-ot web-vitals.js v5-tel: p75 percentilek, eszközosztály-szegmentáció, CrUX BigQuery lekérdezések és attribution build a valódi felhasználói teljesítmény méréséhez.
A Real User Monitoring (RUM) az a folyamat, amelynek során a valódi látogatók böngészőjéből gyűjtjük a Core Web Vitals mérőszámokat (LCP, INP, CLS), majd p75 percentilis alapján elemezzük őket eszközosztály, hálózat és földrajzi régió szerint. A szintetikus tesztek (Lighthouse, WebPageTest) egyetlen, idealizált környezetben futnak, ezért nem mutatják meg, mit lát a felhasználó egy hároméves Androidon, 4G-n, akkumulátorkímélő módban. 2026-ban a Google a CrUX p75 mezei adatait használja a rangsoroláshoz, így a RUM nem opció, hanem alapkövetelmény.
A web-vitals.js v5 könyvtár ~2 KB gzippelt méretben szállítja az LCP, INP, CLS, TTFB, FCP méréseket, és tartalmaz egy attribution buildet a kiváltó DOM-elem azonosítására.
A Google a 75. percentilist (p75) használja: az oldalnak akkor jó az értékelése, ha a látogatások 75%-a a „jó" küszöb alatt van.
A CrUX havi adatkészlete BigQuery-ben publikus, de 100 ms (LCP), 25 ms (INP) és 0,05 (CLS) szemcsézettséggel. Finomabb felbontáshoz a CrUX API vagy saját RUM kell.
2026 májusában a globális Core Web Vitals átmenőképesség 55,9% volt (CrUX, 18,4 millió origin), és asztali gépen az INP p75 jellemzően 120 ms, mobilon 248 ms körül van.
Az onINP({ reportAllChanges: true }) és a navigator.sendBeacon() kombináció megakadályozza az adatvesztést bfcache navigáció és tab-bezárás közben.
A percentilist mindig eszközosztály, csatlakozási sebesség és útvonal-sablon szerint szegmentáld. Egy aggregált p75 elrejti a valódi problémákat.
Mi az a Real User Monitoring és miért különbözik a CrUX-tól?
A Real User Monitoring lényege, hogy a saját oldalad JavaScript-kódja méri minden látogató tényleges élményét, és visszaküldi az adatokat egy elemző végpontra. A Chrome User Experience Report (CrUX) ezzel szemben a Google által, opt-in Chrome-felhasználóktól gyűjtött adat, ami 28 napos gördülő ablakban frissül, és csak Chrome-on, csak bizonyos forgalmi küszöb felett áll rendelkezésre. A különbség gyakorlati: a CrUX nem tudja megmondani, hogy a tegnap kiadott deploy elrontotta-e az LCP-t, és nem látja a Safari, Firefox vagy WebView felhasználókat.
A saját RUM-od valós időben, percenként frissül, beépíthető A/B-tesztelésbe, és tetszőleges dimenzió szerint szegmentálható: kampány, kísérlet, bejelentkezett vs. anonim, geo, eszköztípus. Tapasztalatom szerint a csapatok 90%-a addig nem talál meg INP-regressziókat, amíg saját RUM-ot nem épít, mert a CrUX p75-je túl tompa ahhoz, hogy egy 30 ms-os, csak Galaxy A-szérián jelentkező lassulást észrevegyen. (Pont egy ilyen regressziót vadásztam végig egy múltkori projektben, csak Lighthouse-szal sosem találtam volna meg.) A CrUX kiváló versenyző-benchmarkhoz, a saját RUM viszont nélkülözhetetlen a napi mérnöki munkához. A kettő nem helyettesíti egymást, hanem kiegészíti.
A szintetikus mérés (Lighthouse) ezek mellett is hasznos marad, de főleg regressziódetektálásra és diagnosztikára. A rangsorolás szempontjából a Google kizárólag a CrUX-mezőadatokra támaszkodik, így egy 100-as Lighthouse-pontszám és egy elbukott CrUX-értékelés simán együtt létezik ugyanazon az URL-en. Ha eddig csak Lighthouse-szal dolgoztál, érdemes mellé tenni egy RUM-réteget. A kettő együtt adja ki a teljes képet.
A web-vitals.js v5 beállítása lépésről lépésre
A web-vitals JavaScript könyvtár a Google Chrome csapatának hivatalos megoldása a Core Web Vitals kliens oldali mérésére. Mérete ~2 KB gzippelt, tree-shakelhető, és a PerformanceObserver API-ra épül. A telepítés egyszerű:
npm install web-vitals@5
Az alapvető használat egyetlen modulból áll, az összes metrika regisztrálva, eredmények egy sendToAnalytics() függvényen keresztül:
// rum.js — töltsd be defer attribútummal a <head> végén
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
// A body egyszerre több metrikát fűzhet össze, így spóroljuk a hálózati overheadet
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
delta: metric.delta,
id: metric.id, // egyedi metric ID, hogy ne számoljuk duplán
navigationType: metric.navigationType,
url: location.pathname,
deviceMemory: navigator.deviceMemory ?? null,
effectiveType: navigator.connection?.effectiveType ?? null,
ts: Date.now(),
});
// sendBeacon: tab-bezárás közben is megbízhatóan kiküldi az adatot
(navigator.sendBeacon && navigator.sendBeacon('/rum', body)) ||
fetch('/rum', { body, method: 'POST', keepalive: true });
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
Két buktató, amibe sokan beleesnek. Az első: ne hívd ugyanazt az onINP() vagy onLCP() függvényt többször ugyanazon az oldalbetöltésen, mert minden hívás egy új PerformanceObserver példányt regisztrál, ami SPA-kban memóriaszivárgáshoz vezethet. A második: ha SPA-routereddel soft-navigációkat kezelsz, használd a reportAllChanges: true opciót, és figyeld a navigationType: 'soft-navigation' értéket. Részletes INP-hangoláshoz lásd az INP optimalizálás 2026 útmutatót.
Attribution build: melyik DOM-elem okozza a rossz LCP-t és INP-t?
Az alap web-vitals csomag megmondja, hogy a p75 LCP-d 4,2 másodperc, de azt nem, hogy melyik elem és melyik fázis a felelős. Az attribution build pontosan ezt adja meg. A használat ugyanaz, csak a modult cseréld le:
import { onLCP, onINP, onCLS } from 'web-vitals/attribution';
onLCP((metric) => {
const a = metric.attribution;
console.log({
element: a.target, // a CSS selector az LCP elemre
url: a.url, // az LCP forrás URL-je (kép, betűtípus stb.)
ttfb: a.timeToFirstByte, // szerver oldali késleltetés
resourceLoadDelay: a.resourceLoadDelay, // mikor kezdődött a letöltés
resourceLoadDuration: a.resourceLoadDuration, // meddig tartott
elementRenderDelay: a.elementRenderDelay, // főszál blokkolás render előtt
});
});
onINP((metric) => {
const a = metric.attribution;
console.log({
eventTarget: a.interactionTargetElement, // amire kattintottak
eventType: a.interactionType, // 'pointer' | 'keyboard'
inputDelay: a.inputDelay, // input → handler kezdés
processingDuration: a.processingDuration,// handler futása
presentationDelay: a.presentationDelay, // commit → következő frame
longAnimationFrameEntries: a.longAnimationFrameEntries, // LoAF entries
});
});
Ez a felbontás megmondja, hogy az LCP-d azért 4,2 másodperc, mert a timeToFirstByte 1,8 másodperc. Vagyis nem a kép a hibás, hanem a szerver vagy a CDN. Ha viszont a resourceLoadDuration a domináns, a képformátum vagy a CDN gyorsítótár-stratégia a következő lépés. A backend késleltetés csökkentéséről részletesen írtam a TTFB optimalizálás és gyorsítótárazási stratégiák cikkben.
Az INP attribution 2026-ban kapta meg a Long Animation Frames (LoAF) integrációt, ami megmutatja, melyik script vagy stíluslap blokkolta a fő szálat egy interakció során. Ez korábban csak Chrome DevTools-ban volt látható, most már RUM-ban is. Őszintén szólva ez a legfontosabb diagnosztikai fejlesztés az elmúlt két évben.
Adatküldés sendBeacon és Visibility API segítségével
A leggyakoribb RUM-implementációs hiba, hogy a metrikákat fetch()-csel küldjük, és amikor a felhasználó bezárja a tabot, a kérés félbeszakad. Az LCP gyakran csak pagehide eseménynél véglegesedik, az INP pedig csak akkor, ha a felhasználó minden interakcióját végrehajtotta. A megoldás: navigator.sendBeacon(), ami a böngészőnek átadja a kérést, és az még tab-bezárás után is kiküldi.
// Hibás minta: fetch tab-bezáráskor elveszhet
fetch('/rum', { method: 'POST', body });
// Helyes minta: sendBeacon + visibilitychange fallback
function flush(metric) {
const body = new Blob([JSON.stringify(metric)], { type: 'application/json' });
if (!navigator.sendBeacon('/rum', body)) {
// Egyes böngészők visszautasítják, ha a payload > 64 KB
fetch('/rum', { body, method: 'POST', keepalive: true });
}
}
// Mentsd el az utolsó értéket, és csak page-hide-kor küldd
let lastINP = null;
onINP((m) => { lastINP = m; }, { reportAllChanges: true });
addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden' && lastINP) {
flush(lastINP);
lastINP = null;
}
});
Mit jelent a p75 és milyen percentileket érdemes követni?
A 75. percentilis (p75) azt jelenti, hogy a látogatások 75%-ánál a metrika értéke az adott küszöb alatt van. A Google hivatalosan ezt használja a Core Web Vitals értékeléséhez: az oldalnak akkor „jó" a státusza, ha p75 LCP ≤ 2,5 s, p75 INP ≤ 200 ms, p75 CLS ≤ 0,1. A p75 kompromisszum az átlag és a p95 között. Az átlagot szélsőértékek torzítják, a p95 viszont túl szigorú lenne a legrosszabb 1G-s kapcsolatokhoz.
Mérnöki munkához viszont nem elég csak a p75-öt nézni. Én a következő percentileket követem párhuzamosan: p50 (medián, a tipikus élmény), p75 (a Google rangsorolási küszöbe), p95 (a hosszú farok, ahonnan a panaszok érkeznek), és p99 (ami szinte mindig egy hiba, beragadt main thread, infinite loop, vagy egy szélsőséges eszköz). Ha a p50 és p75 közti távolság megnő, az eloszlás romlott, nem csak az átlag. Ha a p95 ugrik, de a p75 nem, akkor egy specifikus szegmens (pl. egy ország vagy egy eszköz) érintett.
Metrika
Jó (p75)
Fejleszthető (p75)
Gyenge (p75)
LCP
≤ 2,5 s
2,5 – 4,0 s
> 4,0 s
INP
≤ 200 ms
200 – 500 ms
> 500 ms
CLS
≤ 0,1
0,1 – 0,25
> 0,25
FCP
≤ 1,8 s
1,8 – 3,0 s
> 3,0 s
TTFB
≤ 0,8 s
0,8 – 1,8 s
> 1,8 s
Eszközosztály, hálózat és útvonal szerinti szegmentáció
Egy aggregált p75 INP, amiben benne van egy iPhone 15 Pro és egy 2021-es entry-level Android, csaknem mindig hazudik. Az iPhone 15 Pro p75 INP-je tipikusan 80 ms, ugyanaz az oldalon entry-level Androidon 350 ms. Ha a forgalom 70%-a iPhone-ról jön, a p75 200 ms alatt marad. Pedig az Android felhasználók 75%-a poor INP-t lát. A megoldás: szegmentálj minden RUM lekérdezést legalább három tengely mentén:
Eszközosztály: használd a navigator.deviceMemory (1, 2, 4, 8 GB) és navigator.hardwareConcurrency értékeket. Nyers, de jobb, mint a User-Agent. Az „alacsony" osztály: ≤ 4 GB memória és ≤ 4 mag.
Hálózati osztály: navigator.connection.effectiveType ('slow-2g', '2g', '3g', '4g'). A 4G-n belüli különbség is óriási, de ennél finomabb adat csak RTT-ből és downlinkből szedhető ki.
Útvonal-sablon: ne pageURL szerint, hanem útvonal-sablon szerint (/termek/:id), különben minden URL külön sorba kerül, és nem aggregálható.
BigQuery- vagy ClickHouse-alapú RUM-ban az APPROX_QUANTILES(value, 100)[OFFSET(75)] adja a p75-öt szegmensenként. Egy minimális minta:
SELECT
route_template,
device_class,
effective_type,
APPROX_QUANTILES(value, 100)[OFFSET(75)] AS p75,
APPROX_QUANTILES(value, 100)[OFFSET(95)] AS p95,
COUNT(*) AS samples
FROM rum_events
WHERE metric = 'INP'
AND ts >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
GROUP BY route_template, device_class, effective_type
HAVING samples >= 100 -- statisztikai zaj küszöb
ORDER BY p75 DESC;
CrUX BigQuery lekérdezések valódi versenyző-összehasonlításhoz
A CrUX havi adatkészlete BigQuery-ben publikus, és ez az egyetlen mód arra, hogy lekérdezd a versenytársaid mezei teljesítményét. A táblák a chrome-ux-report projekt alatt vannak; a legfontosabb a chrome-ux-report.materialized.country_summary és az experimental.country (kísérleti metrikákhoz, pl. soft navigation). Egy egyszerű lekérdezés a saját és a versenyző origin p75 LCP-jére:
SELECT
yyyymm,
origin,
device,
p75_lcp,
p75_inp,
p75_cls
FROM `chrome-ux-report.materialized.device_summary`
WHERE origin IN (
'https://sajatdomain.hu',
'https://versenytars-egy.hu',
'https://versenytars-ketto.hu'
)
AND yyyymm >= 202601
ORDER BY yyyymm DESC, origin, device;
2026 májusi adatok szerint globálisan az originek 55,9%-a teljesíti mind a három Core Web Vitals küszöböt; az LCP átmenőképesség 68,6%, a CLS 81,3%, az INP 86,6%. Asztali gépen az INP közel univerzálisan jó (96,8%), mobilon viszont a p75 INP médián 248 ms. Vagyis az átlagos mobiloldal a peremen táncol. Ha az oldalad mobil-domináns, a RUM-od első dolga az INP-szegmentáció legyen.
Integráció GA4, Cloudflare és Vercel Speed Insights felé
A web-vitals.js modulárisan illeszkedik bármilyen analytics rendszerhez. GA4-hez:
A Cloudflare Web Analytics és a Vercel Speed Insights a web-vitals.js-t építik be alapból. Ha ezek valamelyikét használod, nem kell saját végpontot is fenntartanod, de cserébe nem kapsz tetszőleges szegmentációt. Saját RUM mellett a Cloudflare/Vercel inkább redundancia és gyors A/B vizualizáció. A bfcache-helyreállítások (navigationType === 'back-forward-cache') külön kategóriát érdemelnek. Ezek látszólag villámgyorsak (LCP < 100 ms), és ha nem szűröd ki őket, hamis biztonságérzetet adnak. (Az első RUM-omnál pont ebbe szaladtam bele, hetekig azt hittem, lett valami csoda-optimalizáció.)
Gyakori RUM-implementációs hibák és elkerülésük
A leggyakoribb hibák, amiket kódellenőrzéseken újra és újra látok: (1) a script <script src="..."> blokkoló módban a <head>-ben, ami önmagában rontja az LCP-t (mindig defer vagy modul); (2) ugyanaz a metrika többször regisztrálva SPA route-változásnál, ami memóriaszivárgás; (3) hiányzó keepalive vagy sendBeacon, ami miatt a bounce-elt látogatók adata elvész; (4) elválasztatlan bfcache-helyreállítás, ami torzítja a p75-öt; (5) URL-szintű aggregáció útvonal-sablon helyett. Soft navigációknál a Speculation Rules API előtöltéssel kombinálva drámaian javítja a navigációs LCP-t, de a RUM-nak meg kell tudnia különböztetni a prerenderelt és a hagyományos navigációt. A metric.navigationType mező pontosan ezt szolgálja.
Végül egy gyakorlati tanács: a RUM-od első verziója ne akarjon tökéletes lenni. Engedj el egy MVP-t egy egyszerű végponttal és napi p75 grafikonokkal, a percentilekkel és szegmentációval később játszhatsz. Az igazi értéket az adja, hogy mostantól látod, mit lát a felhasználó, nem az, hogy szépen formázott a dashboard.
Gyakran ismételt kérdések
Mi a különbség a RUM és a szintetikus monitoring között?
A szintetikus monitoring (Lighthouse, WebPageTest) egyetlen, kontrollált környezetben futtat tesztet, ezért reprodukálható, de nem tükrözi a valós felhasználói élményt. A RUM a tényleges látogatók böngészőjéből gyűjti az adatokat, ezért lefedi az összes eszközt, hálózatot és viselkedést, viszont zajosabb és statisztikai elemzést igényel.
A web-vitals.js használata lassítja az oldalt?
Nem érdemben. A könyvtár ~2 KB gzippelt, és kizárólag passzív PerformanceObserver API-t használ. Ha defer attribútummal töltöd be, semmilyen mérhető hatása nincs az LCP-re vagy az INP-re.
Miért különbözik a saját RUM-om és a CrUX p75 értéke?
Több oka lehet. A CrUX csak Chrome-felhasználókat lát, 28 napos gördülő ablakban aggregál, és csak bizonyos forgalmi küszöb felett közöl adatot. A te RUM-od minden böngészőt mér, valós idejű, és lehet, hogy más útvonalakra szűr. Tipikusan a saját RUM volatilisabb, de időben hamarabb mutatja a regressziókat.
Hány RUM-mintára van szükség statisztikailag megbízható p75-höz?
Egy szegmensen belül legalább 100, ideálisan 1000+ minta heti szinten. 100 alatt a p75 erősen ingadozik, és napi ugrások hamis riasztásokat generálnak. Ha egy szegmensben kevesebb adatod van, vond össze egy szélesebb szegmenssel, vagy nézz heti aggregációt.
Hogyan mérjem a Core Web Vitals értéket SPA-ban soft navigációknál?
A web-vitals.js v4 óta kezeli a soft navigációkat: a navigationType: 'soft-navigation' jelzi, ha az új mérés egy kliens-oldali route-váltás után érkezik. Engedélyezd a reportAllChanges: true opciót az onINP()-nél, és a route-váltáskor bekért metrikákat külön kulcson logold, hogy ne keverd össze a hagyományos navigációkkal.
A Cumulative Layout Shift (CLS) csökkentésének gyakorlati útmutatója 2026-ra: aspect-ratio, font-display: optional, content-visibility és RUM mérés web-vitals.js v4-gyel. Valós kódpéldák képekhez, fontokhoz és SSR streaminghez.
Gyakorlati LCP útmutató 2026-ra: a TTFB, a render-blokkoló erőforrások, a fetchpriority és az AVIF képoptimalizálás, valamint a Soft Navigation LCP mérése SPA-kban. Élesben tesztelt példák és kódrészletek.
A Speculation Rules API JSON szabályokkal jelzi Chrome 121+ böngészőnek, mely URL-eket renderelje elő vagy töltse le előre. Tanuld meg a prerender, prefetch és eagerness beállításait gyakorlati kódpéldákkal a Core Web Vitals javításához.