Красная зона в PageSpeed: как на самом деле исправить LCP, INP и CLS на фронтенде
Сайт летает на локальном сервере, синтетический тест в Lighthouse на десктопе выдает гордые 95+ баллов, а в Google Search Console и реальном отчете CrUX висит предупреждение: страницы провалили Core Web Vitals. Знакомая история для любого фронтендера. Синтетический замер на мощном ноутбуке с гигабитным каналом не имеет ничего общего с реальностью, где пользователь открывает сайт со среднебюджетного смартфона через нестабильный мобильный интернет.
После того как Google окончательно заменил старый FID на метрику INP (Interaction to Next Paint), требования к чистоте клиентского кода выросли на порядок. Теперь мало просто быстро показать первый экран — интерфейс обязан моментально откликаться на любые нажатия на протяжении всей сессии. Разберем практические приемы оптимизации LCP, INP и CLS без общих советов в стиле «сожмите картинки и включите gzip».

Оптимизация LCP: вытаскиваем главный элемент из хвоста очереди
Метрика Largest Contentful Paint фиксирует время до полной отрисовки самого массивного видимого блока первого экрана: баннера акции, фонового постера или крупного заголовка. Чаще всего просадки случаются из-за того, что браузер слишком поздно нащупывает этот ресурс.
- Приоритетная загрузка через fetchpriority=”high”. Если ключевым LCP-элементом служит изображение, снимите с него ленивую загрузку
loading="lazy". На первом экране это грубая ошибка, которая замораживает скачивание до момента прохода парсера по DOM и крадет до полутора секунд. Добавьте в тег<img>атрибутfetchpriority="high"либо пропишите предзагрузку в шапке документа:<link rel="preload" as="image" href="..." fetchpriority="high">. - Отказ от CSS-свойства background-image. Изображение, заданное через стили, парсер обнаружит только после того, как скачает и разберет весь CSS-файл. Перенесите фоновые баннеры в семантическую разметку через тег
<img>со стилемobject-fit: cover. В таком случае браузерный сканер предварительной загрузки (Preload Scanner) отправит запрос за файлом мгновенно, еще до построения стилей. - Инлайнинг Critical CSS. Браузер блокирует рендеринг страницы до завершения загрузки таблиц стилей. Минимально необходимый набор CSS для отображения шапки и первого экрана стоит встроить напрямую в тег
<style>внутри<head>, а тяжелые второстепенные стили подгружать асинхронно черезrel="preload".
Как исправить INP: разгружаем главный поток от Long Tasks
Метрика INP фиксирует задержку между действием пользователя (кликом по кнопке, раскрытием фильтра, тапом по меню) и моментом, когда браузер отрисовал следующий кадр с визуальным откликом. Если обработчик события подвешивает Main Thread дольше чем на 50 миллисекунд (порог Long Task), интерфейс фризит, а метрика в PageSpeed уходит в красную зону.
- Дробление синхронных вычислений через scheduler.yield(). Не пытайтесь прогнать тяжелый цикл внутри одного обработчика клика. Используйте современный вызов
scheduler.yield()(или фолбэк черезsetTimeout), чтобы разбивать объемный расчет на кванты и возвращать управление браузеру для своевременной перерисовки промежуточных кадров. - Контроль стейта во фреймворках. При разработке на React оборачивайте ресурсоемкие мутации состояния в
startTransitionили применяйтеuseDeferredValue. Это позволит браузеру моментально отрисовать нажатие на кнопку или ввод символа в инпут, не дожидаясь завершения перерисовки тяжелого листинга. - Вынос вычислений в Web Workers. Сортировка каталога на несколько тысяч товаров, парсинг жирного JSON-ответа или сложная валидация форм не должны выполняться в UI-потоке. Переносите фоновую математику в воркеры.
- Пассивные слушатели событий. Для событий скролла и тач-взаимодействий всегда передавайте флаг
{ passive: true }в методaddEventListener. Это сообщает браузеру, что обработчик не отменяет стандартную прокрутку черезpreventDefault(), устраняя микрофризы при быстром скролле.
Исправление CLS: фиксируем сетку и динамические блоки
Cumulative Layout Shift оценивает визуальную стабильность. Сдвиги возникают, когда уже отрендеренные элементы внезапно меняют свое положение на экране, из-за чего пользователь промахивается мимо кнопок или теряет строку текста при чтении.
- Явные габариты и свойство aspect-ratio. Все изображения, рекламные слоты, видеоплееры и SVG обязаны иметь жестко прописанные атрибуты
widthиheightлибо CSS-свойствоaspect-ratio. Движок браузера мгновенно зарезервирует правильный прямоугольник в разметке еще до того, как медиафайл начнет скачиваться по сети. - Резервирование контейнеров под динамические виджеты. Плашки согласия на куки, виджеты онлайн-чатов и скрипты сторонней рекламы регулярно вклиниваются в DOM-дерево постфактум. Задавайте родительским блокам минимальную высоту (
min-height) в стилях, чтобы их появление не толкало основной контент вниз. - Синхронизация шрифтовых метрик через @font-face. Переключение с системного шрифта на кастомный WOFF2 часто сопровождается пересчетом высоты строк (FOUT). Используйте
font-display: swapвместе с дескрипторамиsize-adjust,ascent-overrideиdescent-override, чтобы подогнать пропорции дефолтного шрифта под боевой и исключить дергание текста при замене гарнитуры.
Оптимизация Core Web Vitals на боевом проекте — это не попытка обмануть робота ради красивых циферок в отчете, а планомерная работа с очередями рендеринга и профилированием в Chrome DevTools. Когда разметка избавлена от скачков, скрипты не блокируют выполнение пользовательских событий, а тяжелые ресурсы отдаются строго по приоритету, страницы стабильно удерживаются в зеленой зоне без лишней головной боли.
