Core Web Vitals — три метрики Google, официально влияющие на ранжирование с 2021 года. Astro из коробки близок к идеалу, но даже здесь есть точки роста. Этот гайд — исчерпывающий разбор каждой метрики с конкретными приёмами.
Что такое Core Web Vitals
| Метрика | Расшифровка | Хорошо | Плохо |
|---|---|---|---|
| LCP | Largest Contentful Paint | ≤ 2.5 с | > 4 с |
| INP | Interaction to Next Paint | ≤ 200 мс | > 500 мс |
| CLS | Cumulative Layout Shift | ≤ 0.1 | > 0.25 |
Astro SSG из коробки обычно показывает:
- LCP: 0.5–1.2 с ✅
- INP: < 50 мс ✅ (нет JS → нет долгих задач)
- CLS: 0 ✅ (при правильном коде)
1. LCP — Largest Contentful Paint
LCP — время до отрисовки самого большого видимого элемента: обложка, hero-изображение или крупный заголовок.
Главные враги LCP
Изображение обложки без preload и eager:
---
// ❌ Плохо
import { Image } from 'astro:assets';
import cover from '../assets/cover.jpg';
---
<img src={cover.src} alt="..." loading="lazy" />
// ✅ Хорошо: eager + fetchpriority + WebP
<Image
src={cover}
alt="Описание для SEO"
width={1200}
height={630}
format="webp"
quality={85}
loading="eager"
fetchpriority="high"
/> Нет preload для hero-изображения:
---
// src/layouts/ArticleLayout.astro
const { coverUrl } = Astro.props;
---
<head>
{coverUrl && <link rel="preload" as="image" href={coverUrl} fetchpriority="high" />}
<link
rel="preload"
href="/fonts/inter-variable.woff2"
as="font"
type="font/woff2"
crossorigin
/>
</head> Шрифты блокируют рендеринг:
/* ✅ font-display: swap + preload */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-variable.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
} Проверить LCP-элемент в DevTools
new PerformanceObserver((list) => {
const last = list.getEntries().at(-1);
console.log('LCP element:', last.element);
console.log('LCP time:', last.startTime.toFixed(0) + ' мс');
}).observe({ entryTypes: ['largest-contentful-paint'] }); 2. INP — Interaction to Next Paint
INP (заменил FID в 2024) — задержка ответа на любое взаимодействие: клик, тап, ввод.
Почему Astro идеален для INP
У статических страниц практически нет JS в Main Thread. Проблемы начинаются с тяжёлыми островами:
---
// ❌ Плохо: загружается сразу, блокирует Main Thread
import HeavyChart from './HeavyChart.tsx';
---
<HeavyChart data={bigData} client:load />
// ✅ Хорошо: только когда в viewport
<HeavyChart data={bigData} client:visible />
// ✅ Ещё лучше: при простое браузера
<HeavyChart data={bigData} client:idle /> Тяжёлые обработчики — главная ловушка
// ❌ Плохо: синхронная обработка блокирует UI
function handleClick() {
const result = heavyComputation(bigData);
setResult(result);
}
// ✅ Хорошо: уступить управление, потом выполнить
function handleClick() {
setLoading(true);
setTimeout(() => {
const result = heavyComputation(bigData);
setResult(result);
setLoading(false);
}, 0);
} 3. CLS — Cumulative Layout Shift
CLS — суммарный сдвиг элементов во время загрузки страницы.
Изображения без размеров — главная причина CLS
---
// ❌ Плохо: браузер не знает высоту до загрузки
---
<img src="/photo.jpg" alt="Фото" />
// ✅ Хорошо: explicit width + height
<img src="/photo.jpg" alt="Фото" width="800" height="600" />
// ✅ Ещё лучше: через astro:assets (размеры автоматически) import {Image} from 'astro:assets';
import photo from '../assets/photo.jpg'; ---
<Image src={photo} alt="Фото" /> Шрифты вызывают FOUT (Flash of Unstyled Text)
/* size-adjust минимизирует сдвиг при замене шрифта */
@font-face {
font-family: 'FallbackInter';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
}
body {
font-family: 'Inter', 'FallbackInter', system-ui, sans-serif;
} Уведомления — частый виновник сдвига
// ❌ Плохо: вставляется в flow и сдвигает контент
<div style={{ position: 'relative' }}>Уведомление</div>
// ✅ Хорошо: fixed выходит из flow документа
<div style={{ position: 'fixed', bottom: '16px', right: '16px' }}>
Уведомление
</div> Измерение Core Web Vitals
web-vitals библиотека
npm install web-vitals ---
// src/layouts/BaseLayout.astro
---
<script>
import { onLCP, onINP, onCLS } from 'web-vitals';
function report({ name, value, rating }) {
console.log(`${name}: ${Math.round(value)} мс — ${rating}`);
// Отправить в аналитику:
gtag?.('event', name, { value: Math.round(value), metric_rating: rating });
}
onLCP(report);
onINP(report);
onCLS(report);
</script> Инструменты
| Инструмент | Тип данных | Ссылка |
|---|---|---|
| PageSpeed Insights | Лаб + реальные пользователи | pagespeed.web.dev |
| Lighthouse | Лабораторные | DevTools → Lighthouse |
| Chrome Web Vitals | Реальный визит | Расширение Chrome |
| Search Console | Реальные пользователи (агрегат) | search.google.com/console |
| CrUX Dashboard | История тренда | Looker Studio |
Чеклист Core Web Vitals для Astro
LCP:
- Hero/обложка с
loading="eager"иfetchpriority="high" -
<link rel="preload">для hero-изображения в<head> - Шрифты с
font-display: swap+<link rel="preload"> - Формат WebP/AVIF (экономия 30-50% веса)
- TTFB < 200 мс — хостинг на CDN (Cloudflare Pages)
INP:
- Минимум
client:load— заменить наclient:visible/client:idle - Нет синхронных тяжёлых операций в onClick/onChange
- Total Blocking Time < 200 мс в Lighthouse
CLS:
- Все
<img>с явнымиwidthиheight - Использовать
<Image />изastro:assets(авторазмеры) - Зарезервированы места для рекламы / динамического контента
-
font-display: swapдля всех кастомных шрифтов - Уведомления через
position: fixed
Типичный результат после оптимизации
| Метрика | До | После |
|---|---|---|
| LCP | 3.2 с | 0.8 с |
| INP | 350 мс | < 50 мс |
| CLS | 0.18 | 0.02 |
| PageSpeed Mobile | 62 | 98 |
Итог
Astro даёт отличный старт для Core Web Vitals, но нужно контролировать изображения без размеров, лишние client:load острова и тяжёлые обработчики событий. Настройте мониторинг через web-vitals и отслеживайте регрессии при каждом деплое. Подробнее о SEO-стратегии — в статье SEO для Astro.