CLS 0.377 → 0.002 за день: три раунда правок critical CSS

Layout-сдвиг 0.377 на старте сайта. К вечеру того же дня - 0.002. Три правки: размеры логотипа, header-nav в critical CSS, Inter Fallback с size-adjust:107%.

22 марта 2026 я выкатил новый сайт клиниговой компании, в которой веду маркетинг как РОМ. На первом прогоне через Lighthouse мобильный отчёт показал CLS 0.377 - красная зона, конкретно режется ранжирование и в Google, и в Яндексе. К вечеру того же дня - 0.002. Три правки, не переписывая сайт.

Если коротко

  • На старте CLS 0.377. После трёх правок critical CSS - 0.002. Один рабочий день.
  • Правка №1: атрибуты width и height на логотип в шапке. Сэкономила ~40% сдвига.
  • Правка №2: правила для .header-nav перенёс в инлайн critical CSS в <head>. Шапка перестала прыгать.
  • Правка №3: фолбэк-шрифт Inter Fallback на базе Arial с size-adjust:107% и ascent-override:90%. FOUT перестал двигать тексты при догрузке woff2.

Что такое CLS и почему 0.377 - это плохо

Cumulative Layout Shift - это метрика, которую Google ввёл в 2020 году как часть Core Web Vitals. Она считает, сколько раз и насколько сильно содержимое страницы прыгает при загрузке. Прыжок - это когда блок текста или картинка смещаются после того, как пользователь уже видит страницу, и не из-за его действия. Зелёная зона до 0.1, выше 0.25 - красная.

Google и Яндекс учитывают Core Web Vitals в общей оценке качества сайта - не как один-в-один фактор ранжирования, а как сигнал в комплексе. Сайт с CLS 0.377 проигрывает соседям из той же выдачи, даже если контент и ссылки сильнее.

На моём первом замере было 0.377. Это значит, что Lighthouse-роботу казалось, что страница буквально прыгает. Не очень удивительно: я ставил приоритет на быстрый запуск, а не на полировку метрик. Через четыре дня после релиза собрался и за один вечер закрыл все три причины.

Раунд 1: размеры логотипа в HTML

Первое, что выдал Lighthouse в Performance Insights - “Image elements do not have explicit width and height”. Конкретно - логотип в шапке. У меня в header.php было так:

<a href="/" class="header__logo">
  <img src="/logo.svg" alt="Компания" />
</a>

Браузер не знает, какого размера логотип будет на готовой странице, пока его реально не скачает. И пока не скачает - резервирует место по дефолту. Когда логотип догружается, его реальный размер «выталкивает» соседние блоки. Шапка резко сдвигается на пол-секунды, и за этим уезжает hero.

Правка элементарная:

<a href="/" class="header__logo">
  <img src="/logo.svg" alt="Компания" width="180" height="44" />
</a>

Браузер видит атрибуты, считает aspect-ratio 180/44, резервирует место сразу. Когда логотип догружается, никаких сдвигов.

Аналогичная история была с hero-картинкой на главной - <img> без размеров и без CSS-резервирования. Туда тоже добавил width="1200" height="630" и для надёжности style="aspect-ratio: 1200/630". После этих двух правок CLS упал с 0.377 до примерно 0.21. Уже жёлтая зона, но ещё не зелёная.

Урок. Размеры на каждый <img> - самая дешёвая правка в CWV. Поддержка во всех браузерах с 2020 года, никаких рисков. Если у вас старый сайт без них - это первое, что нужно поставить, до всего остального.

Раунд 2: header-nav в critical CSS

После первой правки шапка всё ещё «вырастала» сверху страницы на полсекунды. Я смотрел в Performance-вкладке Chrome DevTools - между First Paint и First Contentful Paint браузер ещё не применял стили .header-nav, поэтому шапка рендерилась как столбик ссылок без флексов, без отступов, без бордеров. И потом, когда CSS-файл догружался, шапка резко принимала вид.

В моём <head> уже был блок инлайн critical CSS - стили body, .hero, h1, базовая типографика. Но правил для шапки там не было. Они лежали в /css/main.css, который грузился отдельным запросом и применялся уже после.

Перенёс блок шапки в инлайн critical:

<style>
  /* ...уже было... */

  /* CRITICAL: header */
  .header { background: #fff; border-bottom: 1px solid #e8e8e8; padding: 14px 0; }
  .header__inner { max-width: 1200px; margin: 0 auto; padding: 0 24px; display: flex; align-items: center; justify-content: space-between; gap: 32px; }
  .header__logo { display: inline-flex; }
  .header-nav { display: flex; gap: 24px; align-items: center; }
  .header-nav a { color: #1a1a1a; text-decoration: none; font-weight: 500; }
  /* + мобильный бургер */
  @media (max-width: 768px) { .header-nav { display: none; } .header__burger { display: block; } }
</style>

Из основного main.css эти же правила убрал - чтобы дубля не было. После пересборки CLS упал с 0.21 до 0.05. Зелёная зона.

Идея простая - всё, что отрисовывается выше fold и видно в первый кадр, должно быть в инлайн critical CSS. Тогда браузер рисует страницу за один проход, без перерисовок.

Раунд 3: Inter Fallback с size-adjust

CLS 0.05 - уже норма, но я хотел загнать в почти-ноль. Оставшийся сдвиг приходил от шрифта. Inter я хостил сам в /fonts/inter.woff2 и подключал так:

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}

body { font-family: 'Inter', Arial, sans-serif; }

Что происходило при загрузке. Первый кадр - браузер видит, что Inter ещё не пришёл, рисует текст Arial. Текст занимает место по метрикам Arial - это другая ширина букв, другая высота строки. Когда woff2 догружается (через 200-400 мс на 4G), браузер мгновенно перерисовывает текст Inter. Метрики меняются: предложение, которое занимало две строки, теперь занимает полторы. Блок под ним поднимается вверх на 28 пикселей. CLS считает это сдвигом.

Решение - фолбэк-шрифт с такими же метриками, как у целевого. Я взял Arial как базу (есть на всех системах, не нужно качать) и подкрутил его через size-adjust и ascent-override так, чтобы метрики совпали с Inter.

Вот рабочая комбинация:

@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}

body { font-family: 'Inter', 'Inter Fallback', Arial, sans-serif; }

Что это делает. Браузер сначала пробует Inter - пока его нет, идёт по стеку дальше. Видит «Inter Fallback» - это, по сути, Arial с растянутыми метриками. Текст занимает столько же места, сколько займёт Inter после догрузки. Когда настоящий Inter догружается через 300 мс, текст просто меняет внешний вид, но не сдвигает разметку - метрики уже совпадают.

Где взять значения. Я брал из репозитория Capsize, там есть готовые таблицы для популярных пар «целевой шрифт + Arial». Для Inter с Arial рабочие: size-adjust:107%, ascent-override:90%, descent-override:22%. Если у вас другой основной шрифт - нужно посчитать через калькулятор Capsize или fontkit.

После этой правки CLS упал до 0.002. Lighthouse мобильный - зелёная зона, поле в отчёте окрасилось зелёным. Я ради спортивного интереса прогнал ещё через PageSpeed Insights на field data Чрома - там тоже 0.002 после трёх дней накопления.

Что замерили после

МетрикаДоПосле
CLS Lighthouse mobile0.3770.002
CLS PageSpeed field data(нет, новый сайт)0.002 (через 3 дня)
LCP mobile4.5 с2.4 с (попутно с CLS - за счёт preload hero)
Lighthouse Performance~6093

Параллельно с CLS на третьем раунде я добавил <link rel="preload" as="image" href="/hero.webp" fetchpriority="high"> для hero-картинки - это снизило LCP. Это не часть CLS-истории, но картинка вытянулась попутно.

Если бы делал заново - в день один, не на 4-й

Главный вывод. Все три правки можно было сделать на старте, ещё до релиза. Они не требуют переписывать сайт, они не требуют перехода на новый стек, они занимают 3-4 часа на двух кофе.

Я их откладывал «потом», и в итоге сайт жил в красной зоне четыре дня. На новом домене эти четыре дня могли стоить мне ранжирования: и Google, и Яндекс берут CWV в первый прогон оценки нового сайта. Если первый замер красный - потом догонять долго.

Чек-лист на день один любого нового сайта:

  • Все <img> с атрибутами width и height. Это правило, а не желательно.
  • Header и hero полностью в инлайн critical CSS. Никаких внешних запросов на стили выше fold.
  • Self-hosted шрифты с фолбэком через size-adjust от системного. Не оставляйте Arial без подкрутки - он не совпадает по метрикам ни с Inter, ни с Roboto, ни с Open Sans.
  • Lighthouse прогон до релиза. Если CLS >0.1 - релиз откладываете, пока не закроете.

Целиком кейс по 50 дням SEO в B2B-клининге - в статье «50 дней SEO в B2B-клининге», там CLS - один из пунктов в широкой картине метрик.

Если у вас сайт с похожей проблемой и нужна точечная помощь - пишите в Telegram @dimik90. По услугам:

Близкое по теме: как я работаю - без воды и 40-страничных КП.

Вопрос-ответ

Частые вопросы

Что такое CLS и какое значение нормальное?
Cumulative Layout Shift - сумма всех неожиданных сдвигов разметки за время загрузки страницы. Зелёная зона до 0.1, жёлтая 0.1-0.25, красная выше 0.25. И Google, и Яндекс используют CLS как один из факторов ранжирования с 2021 года. На моём сайте было 0.377 - то есть Lighthouse считал, что половина экрана прыгает при загрузке.
Почему фолбэк-шрифт влияет на CLS?
Пока браузер качает woff2 Inter, текст рендерится фолбэк-шрифтом (Arial или system-ui). Когда Inter догружается, метрики меняются: ширина букв, высота строки. Текст «передёргивается», блоки под ним сдвигаются вниз или вверх. Это и есть FOUT (Flash of Unstyled Text) - на скорости загрузки 1-2 секунды на мобильных он выдаёт огромный сдвиг.
Что такое size-adjust в @font-face и как это считать?
Это CSS-свойство, которое масштабирует фолбэк-шрифт под целевой, чтобы метрики совпали. Считают так: вычисляют x-height и средние ширины символов у целевого шрифта и у фолбэка, потом подбирают size-adjust (от 80% до 120%) так, чтобы текст занимал одинаковое место. Для пары Inter ↔ Arial рабочее значение - 107% с дополнительным ascent-override:90%. Можно использовать готовый калькулятор fontkit или брать значения из репозитория капитанских шрифтов.
Critical CSS - это что и зачем переносить в него header?
Critical CSS - стили, которые нужны для отрисовки видимой части страницы (выше fold). Их инлайнят прямо в `<head>`, чтобы браузер не ждал загрузки внешнего CSS-файла перед первой отрисовкой. Если стили шапки в отдельном файле - браузер сначала рисует контент без шапки, потом догружает CSS, и шапка «прыгает» сверху. Это даёт сдвиг 0.05-0.15 само по себе. Перенос в critical устраняет этот шаг.
Можно ли так же на CMS, или это только для самописа?
Подход универсальный. На WordPress есть плагины типа Critical CSS by NitroPack или Autoptimize, которые делают то же самое автоматически. На Tilda и подобных конструкторах вы внутрь критического CSS не залезете без выгрузки в код. На Astro/Next.js - это вообще встроенная функциональность сборщика. Размеры логотипа в HTML тоже работают везде: атрибуты `width` и `height` на `<img>` поддерживают все браузеры с 2020 года.

Оцените статью

Дальше

← Все записи