Real User Monitoring (RUM) за Core Web Vitals: Настройка на web-vitals.js през 2026 г.
Практическо ръководство за настройка на Real User Monitoring за Core Web Vitals с web-vitals.js v4 през 2026 г.: sendBeacon, GA4 интеграция, attribution build и CrUX sanity check.
Real User Monitoring (RUM) за Core Web Vitals е техниката за събиране на реални метрики от браузърите на истинските ви потребители (LCP, INP и CLS) вместо да разчитате на синтетични тестове от лаборатория. Настройката отнема под час: инсталирате web-vitals.js библиотеката, извиквате onLCP, onINP и onCLS, изпращате стойностите чрез navigator.sendBeacon към ваш endpoint или към Google Analytics 4. След 28 дни имате поле-данни, които съответстват на това, което Google използва за ранкинг. Ето практическо ръководство за 2026 г.
web-vitals.js v4.2+ (юни 2026) е стандартът за RUM инструментация; поддържа LCP, INP, CLS, TTFB, FCP и Long Animation Frames.
Синтетичните инструменти (Lighthouse, PageSpeed Insights lab data) не съвпадат с полевите данни на CrUX. За ранкинг се брои единствено p75 на реалните потребители.
Използвайте navigator.sendBeacon в visibilitychange, а не fetch() в beforeunload. Последното губи 10–30% от beacon-ите на мобилни устройства.
Attribution build-ът (web-vitals/attribution) добавя контекст като target element за INP или ресурс за LCP. Задължителен е за диагностика в продукция.
Сегментирайте по navigator.connection.effectiveType и device class; един общ p75 крие проблеми при 4G Android потребителите.
CrUX BigQuery dataset се обновява месечно и е безплатен sanity check срещу вашите собствени RUM данни.
Какво е Real User Monitoring и защо е важен
Real User Monitoring (RUM), още известен като passive monitoring или field data, събира метрики за производителност от браузърите на реалните посетители, докато те използват вашия сайт. За разлика от synthetic тестовете (където един роботизиран Chromium зарежда страницата ви от контролирана среда), RUM улавя пълния спектър на реалността: 3G мрежа в българското село, четиригодишен Android с 2GB RAM, потребителят с 47 отворени табове и активна антивирусна програма, която блокира вашия main thread за 400ms при hover.
Google's Chrome UX Report (CrUX) е публична агрегация точно на такива данни от Chrome потребители, които са opt-in за телеметрия. Тя захранва Core Web Vitals в Search Console и е сигнал за ранкинг от 2021 г. насам. Ако вашата собствена RUM инструментация не съвпада с CrUX (обикновено няма да съвпада напълно заради sampling), все още имате нужда от нея, защото CrUX не показва трафик под определен праг и не сегментира по вашите бизнес измерения (checkout стъпка, потребителски тип, A/B бранч).
Честно казано, в моята практика съм виждал сайтове със зелен Lighthouse резултат 95+ и червен CrUX INP, като виновникът беше синхронен tracking script, който се държи различно в реални браузърни разширения. Полевите данни са единственият източник на истина.
RUM срещу synthetic monitoring: каква е разликата
Synthetic monitoring (Lighthouse, WebPageTest, SpeedCurve labs) стартира браузър в контролирана среда с фиксиран network throttling и CPU throttling. Резултатите са възпроизводими, което е ценно за regression detection в CI. RUM е обратното: неопределеност плюс мащаб. Ето сравнение по измеренията, за които мениджърите обикновено питат:
Измерение
Synthetic (Lighthouse)
RUM (web-vitals.js)
Съответства ли на Google ranking сигнал
Не
Да (чрез CrUX-съвместим p75)
Покрива ли реални устройства и мрежи
Не (симулирано throttling)
Да
Улавя ли INP от истински клики
Не (INP изисква взаимодействие)
Да
Възпроизводимост за CI regression
Висока
Ниска (статистически шум)
Разкрива проблем от разширения / антивирус
Не
Да
Разходи при 1M page views
~$50–200/мес
~$5–20/мес (self-hosted endpoint)
Време до първи полезни данни
Секунди
1–4 седмици (за stable p75)
Правилният подход е и двата: synthetic в pull request pipeline, за да хванете regression-и рано, и RUM в продукция за истина. Ако имате бюджет само за едното, изберете RUM. Синтетичните числа са утешителна лъжа, когато не съответстват на това, което Google измерва.
Инсталация на web-vitals.js v4
Библиотеката web-vitals от Google Chrome team е де-факто стандарт. Версия 4.2 (юни 2026) е около 2.1KB gzip за standard build-а и добави поддръжка на Long Animation Frames API за по-добра INP диагностика. Инсталирайте я с npm:
npm install web-vitals@^4.2.0
Ако използвате bundler (Vite, Webpack, Rollup), импортирайте само нужните метрики, за да минимизирате bundle size. Tree-shaking-ът работи чисто:
// src/lib/rum.js
import { onLCP, onINP, onCLS, onTTFB, onFCP } from 'web-vitals';
function sendMetric(metric) {
// metric.name = 'LCP' | 'INP' | 'CLS' | 'TTFB' | 'FCP'
// metric.value = число (ms за LCP/INP/TTFB/FCP, unitless за CLS)
// metric.rating = 'good' | 'needs-improvement' | 'poor'
// metric.id = уникален ID за deduplication
// metric.delta = разлика от последното изпращане (за CLS)
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
id: metric.id,
delta: metric.delta,
navigationType: metric.navigationType,
url: location.pathname,
ts: Date.now(),
});
// sendBeacon е синхронен от гледна точка на браузъра и оцелява при unload
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/rum', body);
} else {
fetch('/api/rum', { body, method: 'POST', keepalive: true });
}
}
onLCP(sendMetric);
onINP(sendMetric);
onCLS(sendMetric);
onTTFB(sendMetric);
onFCP(sendMetric);
Заредете този модул възможно най-рано (идеално като first script tag в <head> с type="module"), за да не пропускате ранни LCP кандидати. Ако сте на Next.js или подобен framework, вкарайте го в root layout-а. За SPA с client-side routing извикайте onLCP и onINP с { reportAllChanges: true }, за да получите метрики за всяка навигация, не само за първата.
Как да изпращам метриките към сървър
Има три реалистични опции: собствен endpoint, готово SaaS решение (SpeedCurve, Sentry Performance, Datadog RUM), или Google Analytics 4 като евтин старт. Ето минимален Node.js/Cloudflare Worker пример, който приема beacon и пише в ClickHouse или Postgres:
// api/rum.js — Cloudflare Worker
export default {
async fetch(req, env) {
if (req.method !== 'POST') return new Response('', { status: 405 });
const body = await req.text();
const metric = JSON.parse(body);
const cf = req.cf || {};
// Обогатяваме с контекст, който клиентът не знае
const row = {
...metric,
country: cf.country,
city: cf.city,
colo: cf.colo, // Cloudflare edge локация; proxy за network latency
user_agent: req.headers.get('user-agent'),
received_at: new Date().toISOString(),
};
// Async вмъкване; не блокирайте response-a
await env.RUM_DB.prepare(
`INSERT INTO metrics (name, value, rating, id, url, country, ua, ts)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
).bind(
row.name, row.value, row.rating, row.id,
row.url, row.country, row.user_agent, row.received_at
).run();
return new Response('', { status: 204 });
},
};
Ключовият момент: върнете 204 No Content възможно най-бързо. RUM endpoint-ите приемат хиляди заявки в секунда и не трябва да блокират браузъра. За заявки над 100k/ден агрегирайте в 10-секундни или 1-минутни buckets още на edge-а, защото суровите row-ове стават непреодолими за query-та след няколко седмици. За dashboards препоръчвам ClickHouse с материализирани views за p75 по route и device class.
Как да изпращам Core Web Vitals към Google Analytics 4
Ако вече имате GA4, можете да получите RUM dashboard безплатно без backend. Изпратете всяка метрика като custom event с metric_value параметър:
import { onLCP, onINP, onCLS } from 'web-vitals';
function sendToGA4(metric) {
// gtag е глобалната функция от GA4 tag-а
gtag('event', metric.name, {
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
metric_id: metric.id,
metric_value: metric.value,
metric_delta: metric.delta,
metric_rating: metric.rating,
// debug полета от attribution build
debug_target: metric.attribution?.element,
debug_url: metric.attribution?.url,
});
}
onLCP(sendToGA4);
onINP(sendToGA4);
onCLS(sendToGA4);
В GA4 после създайте custom dimension за metric_rating и custom metric за metric_value. С Explorations раздела построете free-form report с median, average и percentile 75 на metric_value, филтрирани по event_name = 'LCP'. Забележка: GA4 не поддържа истински percentile aggregation в стандартния UI. За точен p75 експортирайте в BigQuery (безплатно за първите 1M events/месец) и изпълнете APPROX_QUANTILES(metric_value, 100)[OFFSET(75)].
Attribution build за диагностика в продукция
Стандартният build ви казва какво е INP: 320ms. Attribution build-ът ви казва защо: клик на бутон #checkout-submit, blocked от 240ms script execution в vendor.js. Импортирайте от web-vitals/attribution:
import { onINP, onLCP, onCLS } from 'web-vitals/attribution';
onINP((metric) => {
const a = metric.attribution;
sendMetric({
...metric,
// Кой елемент беше кликнат/tapped
target: a.interactionTarget,
// Времеви breakdown в ms
input_delay: a.inputDelay,
processing_duration: a.processingDuration,
presentation_delay: a.presentationDelay,
// Long Animation Frames, най-важната новост в v4
loaf_scripts: a.longAnimationFrameEntries?.flatMap(
f => f.scripts.map(s => ({
source: s.sourceURL,
duration: s.duration,
forced_style: s.forcedStyleAndLayoutDuration,
}))
),
});
});
onLCP((metric) => {
const a = metric.attribution;
sendMetric({
...metric,
element: a.element, // CSS selector на LCP елемента
url: a.url, // Ако е img/video, URL на ресурса
ttfb: a.timeToFirstByte,
resource_load_delay: a.resourceLoadDelay,
resource_load_duration: a.resourceLoadDuration,
element_render_delay: a.elementRenderDelay,
});
});
Attribution добавя ~1.5KB gzip, но си струва многократно. За задълбочени техники по INP оптимизация вижте нашето пълно ръководство за INP оптимизация, а за LCP debugging ръководството за LCP оптимизация. Long Animation Frames API е достъпен в Chromium 123+ и дава script-level breakdown, който преди беше невъзможен без Chrome DevTools trace.
Сегментация по устройство, мрежа и route
Един агрегиран p75 за целия трафик крие сериозни проблеми. iPhone 15 Pro на fiber ще ви даде LCP от 1.2s; Xiaomi Redmi 9 на 3G ще даде 6.8s. Ако 20% от трафика ви е втората група, техните проблеми са скрити под средните числа. Обогатете всеки RUM запис с контекст на клиента:
След това във вашия dashboard стройте отделни p75 линии за 4G+ desktop, 4G mobile, 3G mobile. В моята практика 3G mobile p75 обикновено е 2–4x по-лошо от общия p75. Този сегмент вероятно е и точно този, който Google Search Console flag-ва като "failing" за mobile Core Web Vitals. За допълнителен route-level insight добавете route поле, което извежда логически URL pattern (например /product/[id], а не /product/12345). Суровият pathname прави агрегацията безсмислена.
CrUX като безплатен sanity check
Chrome UX Report е публичният dataset на Google с реални Core Web Vitals от opt-in Chrome потребители, агрегирани месечно на origin и URL ниво. Достъпен е по три начина: PageSpeed Insights UI, CrUX API (безплатно 25 000 заявки/ден) и CrUX BigQuery dataset. За origin-level сравнение с вашата RUM инструментация използвайте официалното CrUX API:
Отговорът съдържа histogram-и по три bucket-а (good/needs-improvement/poor) плюс p75 стойност. Ако вашият RUM p75 за LCP е 1.8s, а CrUX показва 3.2s, вероятно недосемплирате мобилен трафик или ботовете замъгляват числата ви. Разминаването само по себе си е сигнал.
Чести грешки при RUM внедряване
Ето седемте най-чести проблема, които съм диагностицирал в client engagement-и през 2024–2026. Аз лично се спънах в поне четири от тях през първата година, в която пуснах RUM в продукция:
Sampling без stratification. Ако вземате 10% случайна извадка, губите long-tail сегменти. Стратифицирайте: 100% на slow-2g/2g, 10% на 4g.
Игнорирайте bot трафик. Googlebot и SEO crawlers генерират неутрални метрики, които разбиват вашия p75. Филтрирайте по navigator.webdriver и известни bot UA-та още на клиента.
CLS без session aggregation. CLS се измерва като max session window (5s прозорец, 1s gap). Изпращане на всяка отделна shift value е грешно. web-vitals.js го прави правилно, ако не преизобретете колелото.
INP преди page unload. INP се финализира при hide event. Ако изпращате при beforeunload на Safari, INP-то ви ще е изкуствено ниско, защото ще пропуснете късни взаимодействия.
Отсъствие на route dimension. Един p75 за целия сайт е безполезен. Home page-ът и checkout-ът имат напълно различни performance profile-и.
Overlap с ad blockers. Ако вашият RUM endpoint е /api/analytics или подобно име, uBlock Origin ще го блокира. Използвайте неутрални имена като /api/rum или /api/telemetry, или first-party subdomain.
Сравняване на p75 с lab data. Не сравнявайте вашия RUM p75 с Lighthouse резултат; те измерват различни неща. RUM p75 срещу CrUX p75 е валидното сравнение.
За backend-level оптимизации, които подобряват TTFB (компонент на LCP), вижте нашето ръководство за TTFB оптимизация. Комбинирано с RUM ще имате пълна видимост от cache hit rate до финалното взаимодействие.
Често задавани въпроси
Каква е разликата между RUM и synthetic monitoring?
RUM събира метрики от реални потребителски браузъри в продукция, покривайки истински устройства и мрежи. Synthetic monitoring стартира контролиран браузър от лаборатория с фиксирано throttling. RUM отразява Google's ranking сигнал (CrUX); synthetic е за regression detection в CI. И двете имат място в зряла performance стратегия.
Колко данни трябва да събера, преди RUM метриките да са надеждни?
За стабилен p75 на трафик-натоварена страница са ви нужни поне 1000 samples за метрика на 28-дневен прозорец. Това съответства на CrUX методологията. При по-нисък трафик изчакайте 4 седмици и агрегирайте на ниво origin, не URL. p75 при под 200 samples е статистически шумен.
Влияе ли web-vitals.js върху производителността на сайта ми?
Библиотеката е 2.1KB gzip за standard build, 3.6KB с attribution; под 5ms parse/execute на средно мобилно устройство. Използва PerformanceObserver, който е passive и не блокира main thread. Beacon-ите се изпращат чрез sendBeacon, без да пречат на unload navigation. Измеримо влияние е под шума в p75.
Мога ли да използвам Google Analytics 4 за RUM без backend?
Да, това е най-евтиният старт. Изпращайте всяка Core Web Vitals стойност като custom event с value и rating параметри. Ограничението е, че GA4 UI не поддържа истински percentile aggregation; за точен p75 експортирайте суровите events в BigQuery (безплатно до 1M events/месец) и стройте APPROX_QUANTILES query-та.
Защо CrUX данните ми се различават от моите собствени RUM данни?
CrUX включва само Chrome потребители, opt-in за телеметрия, което е около 30–50% от Chrome трафика ви. Вашата RUM инструментация покрива всички браузъри и всички посетители (освен блокирани от ad blocker). Разминаване от 10–30% в p75 е нормално; по-голямо предполага sampling bias или проблем с bot filtering.
Доставете AVIF с WebP/JPEG fallback, srcset, fetchpriority и lazy loading. Намалете теглото на изображенията със 70–90% и подобрете LCP/CLS на сайта си.
Научете как да намалите JavaScript пакета с 30–50% чрез code splitting, tree shaking и lazy loading. Практическо ръководство с примери за Webpack и Vite, визуални инструменти за анализ и стратегии за подобряване на Core Web Vitals (LCP, INP).