Перейти к содержимому

2. Core Web Vitals

Core Web Vitals — это официальный набор метрик Google для измерения качества пользовательского опыта. С 2021 года влияют на SEO-ранжирование.

Время до отрисовки самого крупного элемента (изображение, видео, блок текста).

✅ Хорошо: < 2.5 сек
⚠️ Улучшить: 2.5 – 4.0 сек
❌ Плохо: > 4.0 сек

Что влияет на LCP:

  • Время ответа сервера (TTFB)
  • Блокирующие ресурсы (CSS, JS)
  • Медленные изображения
  • Клиентский рендеринг

Суммарный сдвиг контента во время загрузки (когда элементы “прыгают”).

✅ Хорошо: < 0.1
⚠️ Улучшить: 0.1 – 0.25
❌ Плохо: > 0.25

Типичные причины:

  • Изображения без указанных размеров
  • Реклама с динамическими размерами
  • Веб-шрифты (FOUT/FOIT)
  • Динамически добавляемый контент выше видимой зоны

Задержка реакции на пользовательские действия (клик, тап, ввод с клавиатуры). Заменил FID с марта 2024.

✅ Хорошо: < 200 мс
⚠️ Улучшить: 200 – 500 мс
❌ Плохо: > 500 мс
F12 → Lighthouse → "Analyze page load"
https://pagespeed.web.dev/
// LCP Observer
const 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 Observer
let 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 Observer
const inpObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('INP:', entry.processingStart - entry.startTime);
}
});
inpObserver.observe({ entryTypes: ['event'] });
Окно терминала
npm install web-vitals
import { 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);
<!-- Предзагрузка 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">
<!-- ✅ Всегда указывайте размеры -->
<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>
// ❌ Плохо — тяжелая работа в обработчике
button.addEventListener('click', () => {
const result = heavyComputation(); // блокирует UI
updateUI(result);
});
// ✅ Хорошо — используем scheduler
button.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);
});