2. Core Web Vitals
Core Web Vitals — это официальный набор метрик Google для измерения качества пользовательского опыта. С 2021 года влияют на SEO-ранжирование.
Три ключевые метрики
Заголовок раздела «Три ключевые метрики»LCP — Largest Contentful Paint
Заголовок раздела «LCP — Largest Contentful Paint»Время до отрисовки самого крупного элемента (изображение, видео, блок текста).
✅ Хорошо: < 2.5 сек⚠️ Улучшить: 2.5 – 4.0 сек❌ Плохо: > 4.0 секЧто влияет на LCP:
- Время ответа сервера (TTFB)
- Блокирующие ресурсы (CSS, JS)
- Медленные изображения
- Клиентский рендеринг
CLS — Cumulative Layout Shift
Заголовок раздела «CLS — Cumulative Layout Shift»Суммарный сдвиг контента во время загрузки (когда элементы “прыгают”).
✅ Хорошо: < 0.1⚠️ Улучшить: 0.1 – 0.25❌ Плохо: > 0.25Типичные причины:
- Изображения без указанных размеров
- Реклама с динамическими размерами
- Веб-шрифты (FOUT/FOIT)
- Динамически добавляемый контент выше видимой зоны
INP — Interaction to Next Paint
Заголовок раздела «INP — Interaction to Next Paint»Задержка реакции на пользовательские действия (клик, тап, ввод с клавиатуры). Заменил FID с марта 2024.
✅ Хорошо: < 200 мс⚠️ Улучшить: 200 – 500 мс❌ Плохо: > 500 мсКак измерить?
Заголовок раздела «Как измерить?»1. Chrome DevTools
Заголовок раздела «1. Chrome DevTools»F12 → Lighthouse → "Analyze page load"2. PageSpeed Insights
Заголовок раздела «2. PageSpeed Insights»https://pagespeed.web.dev/3. Programmatically (JavaScript)
Заголовок раздела «3. Programmatically (JavaScript)»// LCP Observerconst lcpObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; console.log('LCP:', lastEntry.startTime);});lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
// CLS Observerlet clsValue = 0;const clsObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsValue += entry.value; } } console.log('CLS:', clsValue);});clsObserver.observe({ entryTypes: ['layout-shift'] });
// INP Observerconst inpObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log('INP:', entry.processingStart - entry.startTime); }});inpObserver.observe({ entryTypes: ['event'] });4. web-vitals библиотека (рекомендуется)
Заголовок раздела «4. web-vitals библиотека (рекомендуется)»npm install web-vitalsimport { onLCP, onCLS, onINP, onTTFB, onFCP } from 'web-vitals';
onLCP(console.log);onCLS(console.log);onINP(console.log);onTTFB(console.log);onFCP(console.log);
// С отправкой в аналитику:function sendToAnalytics({ name, value, id }) { fetch('/analytics', { method: 'POST', body: JSON.stringify({ metric: name, value, id }) });}
onLCP(sendToAnalytics);Оптимизация LCP
Заголовок раздела «Оптимизация LCP»<!-- Предзагрузка hero-изображения --><link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<!-- Инлайн critical CSS --><style> .hero { background: url('/hero.webp') center/cover; }</style>
<!-- Приоритет для LCP-элемента --><img src="/hero.webp" fetchpriority="high" alt="Hero">Оптимизация CLS
Заголовок раздела «Оптимизация CLS»<!-- ✅ Всегда указывайте размеры --><img src="photo.jpg" width="800" height="600" alt="Photo">
<!-- ✅ Аспектное соотношение через CSS --><style> .image-wrapper { aspect-ratio: 16 / 9; overflow: hidden; } .image-wrapper img { width: 100%; height: 100%; object-fit: cover; }</style>
<!-- ✅ Место для рекламы --><div style="min-height: 250px;"> <!-- Ad slot --></div>Оптимизация INP
Заголовок раздела «Оптимизация INP»// ❌ Плохо — тяжелая работа в обработчикеbutton.addEventListener('click', () => { const result = heavyComputation(); // блокирует UI updateUI(result);});
// ✅ Хорошо — используем schedulerbutton.addEventListener('click', () => { // Обновляем UI немедленно showLoadingState();
// Тяжелую работу откладываем scheduler.postTask(() => { const result = heavyComputation(); updateUI(result); }, { priority: 'background' });});
// ✅ Или через setTimeout(0)button.addEventListener('click', () => { showLoadingState(); setTimeout(() => { const result = heavyComputation(); updateUI(result); }, 0);});