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

Це технічна стаття для розробників. Якщо ви керуєте салоном, рестораном або ремісничим бізнесом, краще почніть звідси: скільки насправді коштує сайт і що насправді потрібно вашому сайту.
Показники 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 спричиняють:
- Зображення без явно заданих розмірів — браузер не знає, скільки місця зарезервувати.
- Пізнє завантаження вебшрифтів — текст переформатовується після підміни шрифту.
- Динамічний контент над уже наявним вмістом — банери 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: trueyaml
// 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 годин.
Зв’язатися