17 августа 2026 года мы прогоняли SEO-аудит собственного сайта. По скорости главная выглядела хорошо: на десктопе 1920×1080 первый контент появлялся за 820 мс. Но в той же строке отчета стоял CLS 0,2283, то есть суммарный сдвиг макета первого экрана. Мобильный прогон (390×844) в тот же день показал 0,0000.
Напомним пороги: до 0,1 считается «хорошо», выше 0,25 — «плохо». Значит, 0,2283 формально лишь «требует улучшения», но до плохой зоны оставалось совсем немного. Повторные прогоны в рамках того же аудита дали 0,2284, так что случайным совпадением это не было.
Ниже рассказываем, как мы искали причину, какие распространенные советы по замерам не помогли и как проверить то же самое на своем сайте. Сразу о границах: все лабораторные цифры сняты на главной странице нашего сайта в Chromium при 1920×1080. На другом шаблоне и с другими шрифтами числа будут свои, а вот способ поиска подойдет. Стартовые 0,2283 аудит снял на живом сайте через сеть, а 0,0056 и замеры 3 сентября — на локальной продакшн-сборке, без сетевой задержки до сайта.
Что именно прыгало
Аудит записал один скачок на 739–777 мс после старта загрузки. Правый блок первого экрана, где заголовок и абзац, проседал по высоте с 507 до 468 px. Вместе с ним на 39 px подтягивалось все, что стояло ниже.
Причина скачка — подмена шрифта. Страница сначала рисуется резервным системным шрифтом, а когда догрузится фирменный, браузер перерисовывает текст (font-display: swap). У этих шрифтов разные метрики: высота строки, ширина букв. Из-за этого текст занимает другое место, и соседние блоки сдвигаются.
Почему этого никто не заметил
Первая причина: на телефоне сдвига не было, а главную почти все проверяют именно с телефона.
Вторая причина — полевые данные. За 28 дней до 7 сентября PostHog показал p75 CLS ровно 0 и на мобильных (40 замеров), и на десктопе (24 замера). Учитывались только посетители, которые согласились на cookie. Выборка мала, и большая часть этого окна приходится на время до исправлений. То есть в полевых данных проблема просто не проявилась. Наше предположение (отдельно мы его не измеряли): при повторном визите шрифт уже лежит в кеше браузера и успевает до первой отрисовки, поэтому подмены нет.
Третья причина: скачок длится долю секунды. Разработчик с «теплым» кешем его не видит.
Шаг 1: метрики резервного шрифта (17.08)
В тот же день мы сделали очевидное: добавили резервный @font-face с вертикальными метриками, которые взяли из самого файла шрифта, а не по памяти (ascent-override, descent-override, line-gap-override).
Повторный покадровый замер показал, что этого мало: блок первого экрана все равно проседал с 511 до 468 px. Высоту строки мы выровняли, а скачок от этого почти не изменился.
Шаг 2: единица ch (03.09)
Вторая причина нашлась в одном классе. У абзаца было ограничение ширины max-width: 40ch. Единица ch — это ширина глифа «0» в шрифте, который активен именно сейчас. Так что пока текст набран резервным шрифтом, ширина одна, а после подмены уже другая.
Вот что показал покадровый замер:
| Момент | max-width | Высота абзаца | Строк |
|---|---|---|---|
| 915 мс | 360 px | 112,2 px | 4 |
| 938 мс | 400 px | 84,1 px | 3 |
Контейнер становился на 40 px шире, абзац терял строку, и блок съезжал. Мы заменили 40ch на фиксированные 400px: в фирменном шрифте это та же ширина, так что длина строки для читателя не изменилась. После этого блок менялся уже с 483 до 468 px, а не с 511. Остаточный CLS составил 0,0640, это уже зеленая зона. Однако мы хотели понять, что держит и этот остаток.
Шаг 3: наклоненная декоративная полоса
Остаток давала декоративная полоса с контурными словами, наклоненная через skew. У наклоненного элемента высота рамки равна высоте строки плюс ширина слова, умноженная на тангенс угла. Поэтому любое изменение ширины текста превращается в изменение высоты. Во время подмены шрифта три слова полосы изменили высоту так: 207→212, 203→199 и 179→186 px. По площади сдвига полоса перевешивала весь текст первого экрана.
Мы также поставили контрольный опыт: полностью заблокировали файлы шрифтов. CLS стал ровно 0,0000, значит, сдвиг на 100 % вызывал шрифт и ничего другого искать не нужно было.
Дальше мы проверили стандартные советы. Каждый проверяли замером, а не на глаз:
| Что пробовали | CLS |
|---|---|
size-adjust в резервном шрифте |
0,0640 — без изменений |
| высота строки в пикселях | 0,0640 — без изменений |
font-display: block |
0,0930 — хуже |
font-display: optional + preload |
0,0056 |
size-adjust выравнивает среднюю ширину строки, а на конкретных словах заглавными буквами погрешность остается. Высота строки в пикселях не помогает, потому что наклон снова добавляет ширину к высоте. С block вышло самое интересное: пока шрифт грузится, текст невидим, но место под него браузер считает по резервному шрифту. Когда шрифт приходит, подмена все равно происходит.
Сработал font-display: optional. Браузер ждет шрифт около 100 мс, и если тот не успел, резервный остается до конца загрузки страницы, без подмены. Но сам по себе он испортил бы дизайн: шрифт почти никогда не успевал, и полоса рисовалась бы резервным Arial. Измеренная ширина полосы тогда 839 px вместо фирменных 753. Поэтому мы добавили <link rel="preload"> на файл шрифта, чтобы он начинал грузиться вместе с HTML, а не после разбора CSS. С этим полоса по замеру имеет фирменные 753 px, а сдвига нет.
Окончательный замер: три прогона, локальная продакшн-сборка, 3 сентября. На десктопе CLS 0,0056 при LCP 876 мс, на мобильном CLS 0,0000 при LCP 768 мс.
optional мы дали только декоративной полосе. Основному тексту так делать нельзя: читатель видел бы то один шрифт, то другой в зависимости от скорости сети.
Чего стоило исправление и что нашлось заодно (07.09)
Preload оказался не бесплатным. Через 4 дня PageSpeed на мобильном назвал крупнейшим элементом первого экрана (LCP) именно эту полосу. А ее шрифт, полный файл на 22 КБ, прелоадился на каждой странице сайта. Полосе нужны лишь 17 глифов, так что мы вырезали из того же файла подмножество на 2,8 КБ.
Во время того же разбора в сетевых запросах нашлись еще два шрифта, 58 и 57 КБ. Они грузились на каждой странице, хотя ни один стиль сайта их не использовал: пакет оставался подключенным в корневом шаблоне, хотя его переменные никто не читал. Мы убрали импорт и проверили, что вид текста не изменился.
Как проверить свой сайт за 15 минут
Вам понадобится Chrome и десктопное окно. Мобильный режим проверьте отдельно: у нас проблема была только на одном из двух.
- Откройте DevTools, вкладку Network, включите Disable cache. Так вы увидите страницу глазами нового посетителя.
- Перейдите в Performance и запишите перезагрузку. На дорожке Layout shifts будет каждый сдвиг и элементы, которые двигались. Можно обойтись и консолью, вставив после загрузки:
new PerformanceObserver(list => {
for (const e of list.getEntries()) {
if (!e.hadRecentInput) console.log(e.value.toFixed(4), e.sources.map(s => s.node));
}
}).observe({ type: 'layout-shift', buffered: true });
- Проведите контрольный опыт. В Network щелкните правой кнопкой на файле шрифта → Block request URL (или задайте шаблон
*.woff2в Request blocking) и перезагрузите страницу. Если сдвиг исчез, виноват шрифт, и картинки с баннерами можно не трогать. - Поставьте фильтр Font в Network и выпишите все файлы шрифтов. Затем в Elements → Computed посмотрите Rendered Fonts на нескольких текстовых блоках. Файл, которого нет среди реально используемых шрифтов, — кандидат на удаление.
- Поищите в стилях первого экрана единицу
chв ширинах, а такжеskewилиrotateна блоках с текстом. Обе вещи превращают смену шрифта в изменение размера.
Мы измеряли только собственный сайт на Next.js со шрифтами на своем сервере. В теме OpenCart с Google Fonts механизм подмены тот же, но цифр для такого случая у нас нет.
Чего эти цифры не означают
- 0,0056 — это лабораторный замер на локальной сборке, а не обещание для каждого посетителя. Сеть, кеш и устройство меняют картину.
- Ноль в полевых данных мы не можем записать на счет исправлений: большая часть окна приходится на время до них.
- 12 сентября основные шрифты сайта изменились. Резервные метрики для новых шрифтов считались тем же методом, но повторного покадрового замера первого экрана для этой статьи мы не делали. Все цифры выше относятся к сайту до этого изменения.
- CLS — одна из метрик Core Web Vitals. Стабильный первый экран убирает технический изъян, а не поднимает позиции сам по себе.
Как мы разбираем остальные метрики, описано в разделе о Core Web Vitals, а о сервере и весе страниц — в статье о скорости сайта для бизнеса. Если на вашем сайте первый экран прыгает, а причина неочевидна, посмотрите, как мы работаем с ускорением сайта.




