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, а про сервер і вагу сторінок — у статті про швидкість сайту для бізнесу. Якщо на вашому сайті перший екран стрибає, а причина неочевидна, подивіться, як ми працюємо з прискоренням сайту.




