Skip to content
kobiakov.dev
Назад до блогу

Вебпродуктивність у 2026 році: LCP, CLS, INP, шрифти та Cloudinary

Практичний посібник із досягнення зелених показників Core Web Vitals у 2026 році — реальні вимірювання, оптимізація зображень у Cloudinary, стратегія завантаження шрифтів і нова метрика INP.

Vladyslav Kobiakov4 хв читання
Вебпродуктивність у 2026 році: LCP, CLS, INP, шрифти та Cloudinary

Це технічна стаття для розробників. Якщо ви керуєте салоном, рестораном або ремісничим бізнесом, краще почніть звідси: скільки насправді коштує сайт і що насправді потрібно вашому сайту.

Показники Google Core Web Vitals перестали бути абстрактною темою для SEO, щойно стали сигналом ранжування. У 2026 році відповідність трьом пороговим значенням — LCP менш ніж 2,5 с, CLS менш ніж 0,1 та INP менш ніж 200 мс — це базова вимога для будь-якого сайту, що конкурує за органічний пошуковий трафік. У цій статті йдеться про те, що насправді змінює ці показники на сайті Next.js.

Метрики у 2026 році

У березні 2024 року Google офіційно відмовився від FID (First Input Delay) і замінив його на INP (Interaction to Next Paint). INP вимірює затримку найповільнішої взаємодії під час відвідування сторінки, а не лише першої. Це важливо, адже FID було легко обійти, відклавши виконання JavaScript, тоді як INP виявляє повільні обробники кліків у будь-який момент сеансу.

Актуальні порогові значення:

| Метрика | Добре | Потрібне покращення | Погано | | ------- | -------- | ------------------- | -------- | | LCP | ≤ 2,5 с | 2,5–4,0 с | > 4,0 с | | CLS | ≤ 0,1 | 0,1–0,25 | > 0,25 | | INP | ≤ 200 мс | 200–500 мс | > 500 мс |

LCP: майже завжди винне зображення

Largest Contentful Paint зазвичай визначається головним зображенням або елементом <h1>. На сайті-портфоліо це майже завжди головне зображення.

Підказка priority і попереднє завантаження

Одразу повідомте браузеру, що головне зображення є критично важливим:

// HeroSection.tsx import Image from 'next/image'; export function HeroSection() { return ( <Image src="/hero.webp" alt="Hero" width={1200} height={630} priority // disables lazy loading, adds fetchpriority="high" quality={85} /> ); }
tsx

next/image із властивістю priority автоматично генерує <link rel="preload"> у <head>. Браузер завантажує зображення паралельно з обробкою HTML.

Cloudinary для адаптивних зображень

Передавання зображення шириною 1200 пікселів на мобільний екран шириною 390 пікселів марнує близько 60% трафіку. URL API Cloudinary забезпечує адаптивний розмір без зберігання кількох варіантів:

// lib/cloudinary.ts export function cloudinaryUrl( publicId: string, options: { width: number; quality?: number; format?: string } = { width: 800 } ) { const { width, quality = 'auto', format = 'auto' } = options; return `https://res.cloudinary.com/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/image/upload/w_${width},q_${quality},f_${format}/${publicId}`; }
ts

У поєднанні з властивістю sizes компонента next/image:

<Image src={cloudinaryUrl('projects/hero', { width: 1200 })} sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" fill alt="Project hero" priority />
tsx

Браузер вибирає потрібний розмір відповідно до області перегляду. Екран шириною 390 пікселів завантажить зображення приблизно такої самої ширини, а не варіант на 1200 пікселів.

Діагностика LCP

Використовуйте панель Performance у Chrome DevTools із позначками «LCP candidate» або Lighthouse у Node CI:

npx lighthouse https://kobiakov.dev --output json | \ jq '.audits["largest-contentful-paint"].displayValue'
bash

Корисне правило: якщо елемент LCP — це зображення, його URL має бути в <head> як preload або в початковому HTML, а не додаватися через JavaScript.

CLS: зміщення макета виникає з трьох причин

Cumulative Layout Shift вимірює неочікуване переміщення елементів сторінки. На практиці 90% CLS спричиняють:

  1. Зображення без явно заданих розмірів — браузер не знає, скільки місця зарезервувати.
  2. Пізнє завантаження вебшрифтів — текст переформатовується після підміни шрифту.
  3. Динамічний контент над уже наявним вмістом — банери cookie або реклама, що завантажується із затримкою.

Зображення

Завжди задавайте width і height для <Image>. Адаптивне зображення з fill розміщуйте в контейнері з явно заданим співвідношенням сторін:

<div className="relative aspect-video"> <Image src={src} alt={alt} fill className="object-cover" /> </div>
tsx

aspect-video (16:9) резервує рівно стільки місця, скільки потрібно. Після завантаження зображення нічого не зміщується.

Шрифти

Найгірша стратегія завантаження шрифтів — типова за замовчуванням: без font-display, без preload і зі шрифтами зі стороннього CDN.

// app/layout.tsx — next/font (zero-CLS approach) import { Inter, JetBrains_Mono } from 'next/font/google'; const inter = Inter({ subsets: ['latin'], display: 'swap', // show fallback immediately, swap when ready variable: '--font-inter', preload: true, });
ts

next/font завантажує шрифти під час збірки та розміщує їх на вашому домені. Немає міждоменного запиту й CLS через пізню підміну шрифту. display: 'swap' разом із CSS-властивістю size-adjust (яку next/font додає автоматично) підлаштовує резервний шрифт під метрики основного — візуальне зміщення стає непомітним.

INP: нова заміна FID

INP понад 200 мс зазвичай означає одну з таких проблем:

  • Обробник кліку виконує синхронну роботу в головному потоці.
  • Компонент React надмірно перерендерюється під час взаємодії.
  • Сторонні скрипти блокують головний потік.

Як знайти тривалі завдання

У панелі Performance в Chrome DevTools шукайте завдання довші за 50 мс — вони позначені червоним. У вкладці Activity відфільтруйте «Long tasks».

Розбивайте важку роботу за допомогою scheduler

Якщо під час взаємодії користувача потрібно виконати ресурсомістке обчислення, між частинами роботи повертайте керування браузеру:

async function processLargeList(items: Item[]) { const CHUNK_SIZE = 50; for (let i = 0; i < items.length; i += CHUNK_SIZE) { const chunk = items.slice(i, i + CHUNK_SIZE); processChunk(chunk); // Yield to browser: allows paint + input handling await new Promise((resolve) => scheduler.postTask(resolve, { priority: 'background' })); } }
ts

Зменшуйте кількість повторних рендерів React

Стрибки INP під час фільтрації або пошуку часто спричиняє компонент, який повторно рендерить увесь список. Використовуйте useMemo для похідних даних і React.memo для елементів списку:

const filteredProjects = useMemo( () => projects.filter((p) => p.tags.includes(activeTag)), [projects, activeTag] );
ts

Вимірювання в CI

Не покладайтеся лише на локальні запуски Lighthouse. Продуктивність у реальних користувачів відрізняється. Додайте автоматичні вимірювання до CI-конвеєра:

# .github/workflows/perf.yml - name: Lighthouse CI uses: treosh/lighthouse-ci-action@v12 with: urls: | https://staging.kobiakov.dev/ https://staging.kobiakov.dev/blog budgetPath: ./lighthouse-budget.json uploadArtifacts: true
yaml
// lighthouse-budget.json [ { "path": "/*", "timings": [ { "metric": "largest-contentful-paint", "budget": 2500 }, { "metric": "interactive", "budget": 3500 } ], "resourceSizes": [ { "resourceType": "script", "budget": 300 }, { "resourceType": "image", "budget": 500 } ] } ]
json

Перевищення бюджету зупинить CI-збірку ще до того, як регресія продуктивності потрапить у production.

Короткий огляд швидких покращень

| Зміна | Вплив | | -------------------------------------------------------------- | ----------------------------------- | | Додати priority до головного зображення | LCP від -0,5 до -1,5 с | | Перейти на next/font | CLS від -0,05 до -0,15 | | Задати явні розміри для всіх зображень | CLS майже до нуля | | Cloudinary f_auto,q_auto | LCP від -0,2 до -0,8 с на мобільних | | Відкласти некритичний JS (next/script strategy="lazyOnload") | INP від -20 до -80 мс |

Кожне з цих покращень можна реалізувати менш ніж за 30 хвилин. Разом вони зазвичай піднімають оцінку Lighthouse із 60–70 до понад 95 балів.

Працюєте над чимось подібним?

Я створюю швидкі маркетингові сайти та системи онлайн-запису для малого бізнесу по всій Європі — як незалежний розробник, віддалено. Відповідаю протягом 24 годин.

Зв’язатися
Share: