gora.
įrašas · 2026 m. gegužės 4 d. · 9 min skaityti

Core Web Vitals — LCP, INP, CLS paprasta kalba

Atidarai PageSpeed Insights, įvedi savo domeną, lauki 15 sekundžių. Ir gauni keturis oranžinius apskritimus bei vieną raudoną. Po jais — raidės LCP, INP, CLS, FCP, TTFB. Žemiau — trisdešimties „galimybių“ sąrašas anglų kalba: eliminate render-blocking resources, reduce unused JavaScript, properly size images. Slenki žemyn. Nieko nesupranti. Uždarai.

Aš tai patyriau. Ir su dešimtimis klientų tai patyriau. Todėl išsiaiškinkim, kas iš to tikrai svarbu, o kas — triukšmas.

Iš viso PSI ataskaitos turi reikšmę tik trys raidės: LCP, INP, CLS. Tai Core Web Vitals. Google viešai patvirtino, kad jos įeina į reitingavimo signalus nuo 2021 m. birželio. Ir nuo to laiko tik stiprino jų svorį. Iš bendrųjų 30+ SEO faktorių Core Web Vitals — vienintelis dalykas, kurį Google oficialiai matuoja per realių vartotojų naršykles.

Metrikos atsinaujino. 2024 m. kovą INP pakeitė FID (First Input Delay). FID skaičiavo tik pirmojo paspaudimo vėlinimą. INP žiūri į visus apsilankymo metu vykusius sąveikavimus ir paima blogiausius iš jų. Griežčiau, realistiškiau, ir nužudyti jį dabar sunkiau.

Šio straipsnio tikslas — paaiškinti tris metrikas žmonių kalba, pateikti konkrečius tikslus ir parodyti, ką daryti kode. Be vandens apie „pagreitinkite savo svetainę“.


LCP — Largest Contentful Paint

Tai laikas nuo puslapio krovimosi pradžios iki to momento, kai vartotojas pamato didžiausią elementą matomoje ekrano dalyje. Paprastai tai hero paveikslėlis, vaizdo įrašas arba didelis blokas su h1. Naršyklė pati nusprendžia, kas yra didžiausia.

Tikslai:

  • < 2.5s — gerai
  • 2.5–4s — reikia padirbėti
  • 4s — blogai

Tos 2.5 sekundės — ne nuo tavo paspaudimo. Jos nuo to momento, kai naršyklė pradėjo gauti pirmąjį baitą. Tai yra DNS, TCP, TLS, serverio atsakas, HTML parsisiuntimas, parsinimas, paveikslėlio parsisiuntimas, atvaizdavimas — visa tai turi tilpti į 2500 milisekundžių.

Kas paprastai gadina LCP:

Sunkus hero paveikslėlis. Dizaineris atsiuntė JPEG 4000×3000 dydžio 2.8 MB. Tu jį prikabinai be optimizacijos. Naršyklė siunčia 2.8 MB per 4G, LCP nueina už 5 sekundžių. Sprendžiama per next/image su priority, WebP/AVIF, teisingais dydžiais. Next 15 — <Image src={hero} priority placeholder="blur" /> ir viskas.

Render-blocking JS ir CSS. <head> stovi sinchroniniai <script> arba milžiniškas CSS failas. Naršyklė negali atvaizduoti DOM, kol jų neatsisiųs ir neįvykdys. Sprendimas: defer/async ant skriptų, kritinį CSS dėk inline, likusį — atskiru failu su media="print" triuku arba per kritinį CSS.

Lėtas TTFB. Time To First Byte. Serveris galvoja 800 ms prieš atiduodamas HTML. Priežastys: serverio atvaizdavimas be talpyklos, sunki DB, šaltas serverless funkcijos startas, geografiškai tolimas hostingas. Sprendimas: CDN prieš viską (Cloudflare, Vercel Edge), talpinimas puslapio lygyje, ISR statinėms dalims.

Preload trūkumas kritiniams ištekliams. Šriftas, kuris naudojamas h1, pradeda krautis tik po CSS parsinimo. Tarp to — tuščia vieta. Įdėk <link rel="preload" as="font" href="..." crossorigin> pagrindiniam šriftui, ir LCP nukrenta 300–500 ms.

Ko nedaryti: lazy-load ant hero paveikslėlio. Tai klasikinė klaida. Tavo <img loading="lazy"> ant pačios LCP paveikslo, ir jis laukia, kol scroll-observer jį aktyvuos. LCP pakyla į padanges. Ant hero — loading="eager" ir fetchpriority="high".

Dar viena subtilybė: LCP elementas gali keistis puslapio krovimosi metu. Iš pradžių tai h1, paskui atsivaizdavo hero — tapo hero. Paskui užsikrovė vaizdo įrašo viršelis — tapo jis. Naršyklė užfiksuoja galutinį LCP tik tada, kai puslapis nueina į foną arba įvyksta pirmas sąveikavimas. Todėl jei turi animaciją, kuri į hero išvažiuoja po sekundės — LCP skaičiuojamas nuo tos sekundės, o ne nuo pradinio h1. Geresnis anti-pattern — tegul kritinis vizualas matosi iškart.


INP — Interaction to Next Paint

Tai laikas nuo to momento, kai bakstelėjai mygtuką, iki to momento, kai naršyklė parodė kitą kadrą po reakcijos į tą bakstelėjimą. Ne „kai įvykis apdorotas“, o „kai vartotojas pamatė rezultatą“.

Tikslai:

  • < 200ms — gerai
  • 200–500ms — reikia padirbėti
  • 500ms — blogai

200 milisekundžių — tai slenkstis, po kurio žmogaus smegenys suvokia sąsają kaip „stringančią“. Mažiau — reaguoja akimirksniu.

INP skaičiuoja visus sąveikavimus per sesiją (paspaudimus, bakstelėjimus, klavišų paspaudimus) ir paima iš jų 98-ąjį procentilį. Tai yra užtenka vieno sunkaus paspaudimo, kad metrika nukristų.

Kas paprastai gadina INP:

Sunkus JavaScript main thread'e. Paspaudimas ant filtro kataloge paleidžia tvarkytuvą, kuris perskaičiuoja 2000 prekių ir perrenderuoja visą sąrašą. React daro reconciliation 600 ms. Pirštas jau atleido mygtuką — ekrane nieko. Sprendimai: useTransition sunkiems atnaujinimams (React 18+), sąrašų virtualizacija, suskaldymas į gabalus per scheduler.postTask.

Long tasks. Bet kuri užduotis main thread'e ilgesnė nei 50 ms — long task. Jei tavo paspaudimo tvarkytuve yra įvykių sekiklis, kuris sinchroniškai lenda į localStorage, parsina JSON ir siunčia fetch — visa tai blokuoja srautą. Sprendimas: perkelk į requestIdleCallback, iškelk analitiką į web worker.

Layout thrashing. Paspaudimo tvarkytuvas skaito offsetWidth, paskui keičia stilių, paskui vėl skaito offsetWidth. Naršyklė kaskart perskaičiuoja layout. Sudėtingame puslapyje tai duoda 100+ ms iš nieko. Sprendimas: grupuok DOM skaitymus ir rašymus, naudok requestAnimationFrame.

Riebūs trečiųjų šalių skriptai. Intercom, Hotjar, Google Tag Manager. Jie kabina savo tvarkytuvus virš tavųjų. Kiekvienas paspaudimas eina per perėmėjų grandinę. Sprendimas: tingiai krauk analitiką po load, naudok Partytown skriptams iškelti į worker.

Code-splitting irgi padeda, bet ne tiesiogiai — jis mažina ne INP, o JS kiekį, kuris yra parsinamas. Netiesiogiai tai atlaisvina main thread.

Iš praktikos: dažniausiai INP nukrenta dviejuose ekranuose — sąraše su filtrais ir formoje su autocomplete. Ant filtrų padeda įvedimo debounce (300 ms užtenka) plius useDeferredValue rezultatų atvaizdavimui. Ant autocomplete — ankstesnės užklausos atšaukimas nauju paspaudimu (AbortController) ir išskleidžiamojo sąrašo virtualizacija, jei rezultatų daugiau nei trisdešimt.


CLS — Cumulative Layout Shift

Tai visų išdėstymo poslinkių suma per puslapio gyvavimo laiką. Ne laikas, ne milisekundės — bematis skaičius nuo 0 ir aukščiau. Skaičiuojama kaip impact × distance: koks ekrano plotas pasislinko, kiek procentų ekrano jis nuvažiavo.

Tikslai:

  • < 0.1 — gerai
  • 0.1–0.25 — reikia padirbėti
  • 0.25 — blogai

0.1 praktikoje — tai, pavyzdžiui, paveikslėlis ant 30% ekrano, kuris šoktelėjo 30% viewport aukščio. Vienas toks šuolis — ir tu jau ant ribos.

CLS — tai apie „aš spaudžiu mygtuką, o tą akimirką virš jo atsiranda baneris, aš spaudžiu banerį“. Visus erzina. Google tai žino.

Kas paprastai gadina CLS:

Paveikslėliai ir iframe'ai be aiškių dydžių. Jei <img> neturi atributų width ir height (arba jų CSS atitikmenų), naršyklė nežino, kiek vietos rezervuoti. Paveikslėlis užsikrauna — ir nustumia visą turinį po juo. Sprendimas: visada nurodyk width ir height (arba aspect-ratio CSS). Next/Image tai įjungta pagal nutylėjimą, jei perduodi dydžius.

Dinaminiai inject'ai. Slapukų baneris, kuris atsiranda viršuje po sekundės nuo užkrovimo ir nustumia visą puslapį žemyn. AdSense, kuris įterpia bloką į straipsnio vidurį. Bet koks A/B testas, kuris keičia DOM skrydyje. Sprendimas: rezervuok vietą iš anksto. Slapukų baneris — fiksuotas overlay, o ne block element. Reklama — fiksuoto aukščio slotas.

Font swap be size-adjust. Krautosi tinkintas šriftas, iki tol veikia fallback. Jie turi skirtingą eilutės aukštį. Kai šriftas užsikrauna, tekstas pertvarkomas, viskas šokinėja. Sprendimas: font-display: swap + size-adjust @font-face, kad fallback užimtų lygiai tiek pat vietos. Arba font-display: optional, kuris nesvopuoja lėtuose ryšiuose. Next galima per next/font — jis pats apskaičiuoja size-adjust.

Asinchroninis turinys puslapio viršuje. Krautosi rekomendacijų sąrašas per fetch ir įterpiamas virš hero. Hero nuvažiuoja žemyn. Niekada neįterpk turinio virš to, ką vartotojas jau mato. Jei reikia — dėk apačioje, o ne viršuje.


Field vs Lab data

Tai vieta, kur pusė žmonių pasimeta.

PageSpeed Insights rodo du blokus. Viršuje — „Discover what your real users are experiencing“ (field data). Apačioje — „Diagnose performance issues“ (lab data). Tai skirtingi dalykai.

Lab data — tai vienas Lighthouse paleidimas idealiomis sąlygomis. Emuliuojamas Moto G4 lėtu 4G, be plėtinių, be talpyklos, be kitų skirtukų. Atkartojama, patogu derinti, bet realybės neatspindi.

Field data — tai CrUX. Chrome User Experience Report. Google renka anonimines metrikas iš realių Chrome vartotojų (tų, kurie turi įjungtą sinchronizaciją ir istoriją). Agreguoja per paskutines 28 dienas. Jei į tavo svetainę užėjo pakankamai žmonių — Google rodo 75-ąjį jų metrikų procentilį.

Google reitinguoja pagal field. Lab — tik užuomina. Gali turėti 100/100 Lighthouse ir tuo pačiu raudoną CrUX, nes realūs vartotojai ateina iš lėtų 2019 metų Android flagmanų per metro.

Kur žiūrėti field:

  • PageSpeed Insights — paprasčiausias būdas, namų puslapis ir atskiri URL.
  • Search Console → Core Web Vitals report — duoda suskirstymą pagal URL grupes: „paieška“, „prekių kortelės“, „straipsniai“. Matosi, kur būtent nukrenta.
  • PSI API — automatizacijai. Varai per skriptą kartą per savaitę pagal svarbių puslapių sąrašą, dedi į lentelę, kuri dinamiką.
  • CrUX BigQuery dataset — tiems, kas moka SQL. Galima ištraukti pilnus pasiskirstymus, o ne tik procentilius.

Vienas svarbus dalykas. Jei svetainė turi mažai srauto — CrUX neparodys field data. Tiesiog pasakys „nepakanka duomenų“. Tokiu atveju Google reitingavimui naudoja data origin lygyje (viso domeno) arba grįžta į lab. Todėl net jaunoje svetainėje verta išspausti geriausią Lighthouse — tai tavo draudimas, kol CrUX neprisikaupia.



Kas NEPADEDA

Sąrašas dalykų, kuriuos dažnai pataria, bet kurie Core Web Vitals neįtakoja arba įtakoja neigiamai.

CSS minifikacija ir taip lengvoje svetainėje. Tu turi 18 KB CSS, gzip spaudžia tai iki 4 KB. PSI rašo „minify CSS, save 1.2 KB“. Tie 1.2 KB nieko neduos. Veltui gaišti laiką.

Lazy load visko iš eilės. loading="lazy" ant visų paveikslėlių — labiausiai paplitusi klaida po to, kai Chrome įvedė natyvų lazy. Įskaitant hero. Įskaitant logotipą antraštėje. Galiausiai paveikslėliai virš fold laukia intersection observer'io vietoj to, kad krautųsi iškart. LCP auga. Lazy dedasi tik ant to, kas yra žemiau fold.

Agresyvus preload visko, kas juda. Perskaitei straipsnį apie „preload critical resources“, pakabinai <link rel="preload"> ant dešimties failų. Naršyklė krauna juos su prioritetu didesniu nei HTML. Tinklas užkimštas. LCP paveikslėlis laukia savo eilės. Preload — tai aštrus įrankis. Maksimum du-trys kritiniai ištekliai.

Service Worker su talpykla „ant visko“. Talpinimas protingas, bet jei nesupranti, ką talpini — vartotojas mato seną svetainės versiją. Su Core Web Vitals tai neturi nieko bendro apskritai, o problemų prideda.


Išvada

Jei turi ribotą laiką — prioritetai tokie.

LCP pirmiausia. Lėtas puslapis — vartotojai išbėga prieš tai, kai ką nors pamatė. Tai kerta ir per reitingavimą, ir per konversiją tiesiogiai. Hero paveikslėlis per next/image priority, šriftų preload, CDN prieš viską. Tai duoda 80% rezultato.

CLS antruoju. Šokinėjantis išdėstymas erzina ir žudo pasitikėjimą. Ypač mobile. Dydžiai ant media, font-display su size-adjust, neįterpk turinio virš jau matomo. Šios pataisos daromos greitai ir negrįžta.

INP trečiuoju. Tai subtiliausia iš visų ir reikalauja daugiau darbo — profiliavimas, sunkių tvarkytuvų išsiaiškinimas, refaktoringas. Bet be LCP ir CLS tvarkos imtis INP per anksti.

Metrikas žiūrėk field — Search Console ir PSI. Lab naudok tik kaip smėlio dėžę pataisymams patikrinti prieš deploy. Ir atmink: vieno paleidimo nepakanka. CrUX atsinaujina kartą per dieną, agreguoja per 28 dienas. Po pataisymų lauk bent dvi savaites, prieš darydamas išvadas.

Core Web Vitals — LCP, INP, CLS paprasta kalba · hiregora.com