eduardweb.
SEO & PerformanceIntermediar#performance#seo#nextjs#web-vitals

Core Web Vitals în producție: de ce Lighthouse te minte și cum măsori RUM pe bune

De Adrian Voicu, 28 sept. 2026 · 10 vizualizări · 3 like-uri

Postat acum 3 zile
typescript
import { onCLS, onINP, onLCP } from 'web-vitals/attribution';

function sendMetric(metric: any) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
    target: metric.attribution?.interactionTarget || metric.attribution?.largestShiftTarget || null,
    url: window.location.pathname,
  });

  // sendBeacon nu blochează descărcarea paginii la navigare
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/rum', body);
  } else {
    fetch('/api/rum', { body, method: 'POST', keepalive: true });
  }
}

// Raportează doar pe utilizatori reali în producție
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'production') {
  onCLS(sendMetric);
  onINP(sendMetric);
  onLCP(sendMetric);
}

Am crezut mult timp că dacă scot 98 în Lighthouse pe MacBook stau liniștit cu Core Web Vitals. Realitatea m-a lovit la un magazin online cu vreo 45k vizitatori unici pe lună: Google Search Console ne-a trimis notificări pe bandă rulantă că picăm la INP și CLS pe mobil. Lighthouse e un laborator steril; utilizatorii reali au telefoane de 800 de lei și conexiuni 4G instabile prin metrou.

Trioul sfânt: LCP, INP și CLS fără teorie inutilă

Metricile s-au maturizat, iar Google nu mai iartă scripturile care blochează main thread-ul.

LCP (Largest Contentful Paint): Sub 2.5 secunde. De cele mai multe ori buba e o imagine de hero fără priority / fetchpriority="high", sau un font custom care ascunde textul până se descarcă (clasicul flash de text invizibil). Dacă ai CDN bun și optimizezi imaginile la dimensiunea exactă a viewport-ului, de obicei îl rezolvi.

INP (Interaction to Next Paint): A înlocuit complet vechiul FID și e cel mai greu de optimizat. Măsoară timpul scurs de când userul dă tap pe un buton până când browserul desenează efectiv următorul frame. Dacă ai un onClick care re-randează jumătate de arbore React sau rulează calcule grele sincron, utilizatorul simte interfața blocată. Sub 200ms e ținta.

CLS (Cumulative Layout Shift): Sub 0.1. Aici pică majoritatea din cauza bannerelor de cookie-uri injectate târziu, imaginilor fără width și height specificate în HTML sau fonturilor care provoacă salturi mari când se aplică stilul final.

Vercel Analytics vs RUM custom

Pe proiectele Next.js, tentația e să activezi Vercel Speed Insights dintr-un click. Am făcut-o și noi.

Trade-off-ul Vercel Analytics: E genial de simplu și ai grafice curate în dashboard fără să scrii cod. Problema apare la costuri și granularitate. Dacă treci de 100k-200k vizite, planul Pro începe să adune costuri pe data points, iar pe planul gratuit eșantionarea e destul de agresivă. Cel mai mare minus: nu-ți arată exact selectorul CSS sau elementul din DOM care a generat shift-ul la CLS ori long task-ul la INP.

Pentru proiecte medii spre mari, varianta optimă rămâne pachetul oficial web-vitals folosit cu build-ul de attribution. Îți dă exact targetul interacțiunii și elementul vinovat de layout shift, iar datele le trimiți cu navigator.sendBeacon spre propriul endpoint (sau direct în ClickHouse / Grafana / Datadog).

Ce am învățat depanând date reale

La proiectul de care ziceam mai sus, 65% din problemele de INP veneau de la un widget de live chat al unei terțe părți. În Lighthouse nu apărea fiindcă scriptul se inițializa cu un delay de 4 secunde. Însă când userul dădea click pe un filtru de produse exact când chat-ul își injecta iframe-ul, main thread-ul îngheța 400ms.

Fără RUM cu metadate de context (device type, connection speed, URL) cauți acul în carul cu fân săptămâni întregi.

Voi ce folosiți pentru monitorizare în prod: soluții integrate gen Vercel / Cloudflare sau vă colectați singuri metricile?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.