lenec ru

← все посты

Как ускорить Core Web Vitals на Astro

18K

Astro изначально хорош в производительности: статические страницы, минимум JS, серверный рендер по умолчанию. Но «хорошо» и «отлично» — разные вещи. Я недавно делал аудит контентного сайта на Astro 5: исходно LCP 2.4 с, INP 280 мс, CLS 0.18. После двух недель работы — LCP 1.1 с, INP 90 мс, CLS 0.02. Расскажу, что и в каком порядке менял.

Базовые метрики Core Web Vitals

Прежде чем оптимизировать, договоримся о словаре:

  • LCP (Largest Contentful Paint) — время до отрисовки самого большого видимого элемента. Цель: ≤2.5 с.
  • INP (Interaction to Next Paint) — время отклика на пользовательское взаимодействие. Цель: ≤200 мс.
  • CLS (Cumulative Layout Shift) — насколько прыгает контент при загрузке. Цель: ≤0.1.

Меряем в реальной аудитории через Search Console (Core Web Vitals report) или CrUX dataset. Лабораторные замеры (Lighthouse, PageSpeed) полезны для отладки, но не отражают реальные цифры.

LCP: первая большая работа

На том проекте LCP-элементом была обложка статьи — изображение 1200×600 пикселей. Что я сделал:

1. Перевести на современные форматы

---
import { Image } from 'astro:assets';
import cover from '../assets/cover.jpg';
---
<Image
  src={cover}
  alt="Обложка"
  widths={[400, 800, 1200]}
  sizes="(max-width: 800px) 100vw, 800px"
  formats={['avif', 'webp']}
  loading="eager"
  fetchpriority="high"
/>

Astro Image сам сгенерирует AVIF и WebP, fallback на JPG. На моих данных AVIF дал на 35-40% меньший вес по сравнению с WebP. Атрибут fetchpriority="high" заставляет браузер загружать обложку с высоким приоритетом — ускоряет LCP на 200-400 мс.

2. Preload критичных ресурсов

Шрифт, который используется в LCP-элементе, надо предзагрузить:

<link
  rel="preload"
  href="/fonts/inter-var.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

Если шрифт недоступен, браузер сначала рисует системный, потом перерисовывает в кастомный — это и LCP, и CLS.

3. Inline критичный CSS

Astro по умолчанию инлайнит критичный CSS, но если у тебя есть глобальные стили из @import в node_modules — они часто грузятся отдельным файлом. Проверь:

pnpm astro build
ls -la dist/_astro/*.css

Если файлов CSS больше двух — посмотри, что туда попадает. На моём проекте я выкинул один CSS-фреймворк, который тащил 80 КБ ради двух классов.

INP: основа — меньше JS

INP меряет, насколько быстро страница откликается на действие пользователя. На Astro это редко проблема, но бывает.

1. Убрать тяжёлые острова

На том проекте было два client:load компонента в шапке — поиск и переключатель темы. Каждый тащил ~30 КБ JS, и на медленных устройствах гидрация съедала первые 200 мс.

Заменил на client:idle для поиска и client:only="react" для темы (идея: тема нужна только когда пользователь её жмёт, до этого работает CSS). INP упал с 280 до 130 мс.

2. Long tasks

Если у тебя в js обработчик клика делает много синхронной работы (фильтрация большого списка, форматирование), браузер не успевает отрисовать ответ за 200 мс.

Решение — requestAnimationFrame для разбиения работы или scheduler.yield():

async function handleClick() {
  setLoading(true);
  await scheduler.yield();
  const result = heavyFilter(items, filter);
  setItems(result);
  setLoading(false);
}

Атрибут scheduler.yield() — нативный API в современных браузерах, который отдаёт управление в браузер, чтобы тот успел отрисовать.

CLS: невидимая, но злая

CLS — самая обидная метрика. Контент уже на экране, но прыгает, когда подгружается баннер или картинка.

1. Размеры изображений

Каждый <img> и <Image> должен иметь явные width и height или aspect-ratio. Astro Image ставит размеры автоматически — это работает.

Для внешних URL пиши руками:

<img src="https://cdn.example.com/photo.jpg" alt="..." width="800" height="600" loading="lazy" />

2. Шрифты с font-display: swap

По умолчанию это даёт CLS — пока шрифт не загрузился, браузер показывает fallback. Когда подменяет — текст «прыгает».

Решение: font-display: optional + правильный fallback с похожими метриками. Или size-adjust в @font-face:

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2');
  font-display: swap;
  size-adjust: 105%;
  ascent-override: 90%;
}

Эти overrides точно подгоняют fallback к реальному шрифту, и подмена не вызывает прыжка. Цифры подбираются через инструменты типа font-style-matcher.

3. Резервируй место для динамики

Если у тебя баннер-cookie, который появляется через 200 мс — он сдвинет всю страницу. Резервируй место заранее или показывай через overlay (position: fixed).

Server timing и кеш

Если у тебя SSR — обращай внимание на TTFB (Time To First Byte). LCP не может быть хорошим, если сервер отвечает за секунду.

// astro.config.mjs
export default defineConfig({
  output: 'server',
  adapter: node({ mode: 'standalone' }),
  vite: {
    server: {
      headers: { 'Server-Timing': 'cache;dur=12' },
    },
  },
});

На уровне Nginx или CDN кешируй HTML страниц на минуту-две. Это ускоряет TTFB до сотен миллисекунд.

View Transitions и LCP

Если используешь Astro View Transitions, у тебя SPA-навигация. Проверь, что переход не блокирует LCP — иногда плавный fade на 300 мс задерживает отрисовку. Лечится более коротким durations или явным prefetch.

Третьи стороны

Самый частый виновник плохих метрик — сторонние скрипты: аналитика, чаты, баннеры. На том проекте Hotjar тащил 280 КБ и съедал 80 мс на обработку.

Стратегии:

  • Загружать через partytown в отдельном worker'е.
  • Откладывать через defer или загружать после первого взаимодействия.
  • Удалять то, чем реально не пользуются.

Чек-лист, который я применяю

  1. Открыть DevTools → Performance → запись типичного сценария.
  2. Записать LCP, INP, CLS в реальной аудитории через Search Console.
  3. Проверить размеры всех <img> и <Image>.
  4. Подключить шрифты через preload + size-adjust.
  5. Минимизировать client:load компоненты.
  6. Включить кеш на уровне CDN или сервера.
  7. Аудит сторонних скриптов.
  8. Прогнать Lighthouse и сравнить.

Что копать дальше

Web Vitals — это не «один раз настроил и забыл». На контентных сайтах метрики ползут вверх с каждой новой статьёй (больше изображений, больше CSS, больше скриптов). Я раз в три месяца делаю мини-аудит и фиксаю регрессии. Это занимает 2-4 часа и держит сайт в зелёной зоне.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.