Оптимізація шрифтів у 2026: WOFF2, size-adjust і preload для Core Web Vitals

Як оптимізувати веб-шрифти у 2026 році: self-host WOFF2, font-display swap, preload, size-adjust проти CLS та сабсетинг для української локалі. Гід з RUM.

Оптимізація шрифтів 2026: WOFF2 + size-adjust

Оновлено: 26 липня 2026

Оптимізація шрифтів у 2026 році — це поєднання чотирьох технік: WOFF2 з font-display: swap, preload для критичних файлів, size-adjust для fallback-метрик і сабсетинг через unicode-range. Чесно кажучи, у моїй RUM-практиці саме шрифти залишаються найпоширенішою причиною видимого CLS на p75 mobile, навіть коли зображення й реклама вже приборкані. Нижче ви знайдете покроковий гід, який спирається на польові дані з CrUX і практику продакшн-сайтів, а не на синтетичні Lighthouse-скріншоти.

  • Self-hosting WOFF2 через власний домен майже завжди швидший за Google Fonts CDN у 2026: це підтверджують CrUX-дані та відсутність third-party connection.
  • font-display: swap усуває невидимий текст (FOIT), але без size-adjust провокує CLS у момент підміни шрифту, тож потрібні обидва інструменти.
  • <link rel="preload" as="font" crossorigin> застосовуйте тільки до 1–2 критичних шрифтів above-the-fold; надмірний preload погіршує LCP.
  • Variable fonts у форматі WOFF2 замінюють 4–6 окремих файлів одним і зменшують загальну вагу шрифтів на 30–70%.
  • Сабсетинг через unicode-range та інструмент pyftsubset прибирає до 90% гліфів, які браузер ніколи не завантажить для української локалі.
  • Fallback-метрики (size-adjust, ascent-override, descent-override) є стандартним способом обнулити CLS від підміни шрифту в Chrome, Firefox і Safari 17+.

Чому шрифти впливають на Core Web Vitals

Шрифти є критичним ресурсом, від якого залежать одразу три метрики: LCP (коли LCP-елемент є текстовим блоком), CLS (коли fallback-шрифт має іншу висоту рядка й ширину гліфів) та INP (коли головний потік блокується під час перекладки макета). У моїх RUM-даних близько 42% сайтів на p75 mobile мають LCP-елемент саме як текст: заголовок, hero-параграф або великий CTA. Це означає, що затримка завантаження шрифту напряму зсуває LCP у червону зону (>2.5 с).

Класичний ланцюжок деградації виглядає так. HTML-парсер знаходить у CSS правило font-family, але не завантажує шрифт одразу, а тільки коли зустрічає елемент, що використовує це сімейство. Далі браузер за замовчуванням затримує рендер тексту (FOIT, тобто Flash of Invisible Text) до 3 секунд у Chrome. Якщо шрифт прийшов пізно, текст рендериться з fallback, а потім різко «стрибає», і саме це є CLS від шрифтів.

Field-data правило: якщо у вашому CrUX-звіті CLS-p75 > 0.05, а зображення й реклами вже стабілізовані, наступний підозрюваний, це шрифти. Загальний огляд Core Web Vitals і як їх діагностувати я вже розбирала у повному гіді з оптимізації CLS у 2026 році.

Self-hosting проти Google Fonts у 2026

Коротка відповідь: self-hosting майже завжди швидший. У 2018–2020 роках Google Fonts мав перевагу через спільний кеш браузерів (якщо користувач уже завантажив Roboto на іншому сайті, ваш сайт отримає його миттєво). Але з жовтня 2020 Chrome ізолював HTTP-кеш за origin (HTTP cache partitioning), і цей аргумент помер. Тепер запит до fonts.googleapis.com запускає окремий DNS-резолвінг, TLS-хендшейк і ще один redirect до fonts.gstatic.com.

У польовій статистиці 2026 року self-hosted шрифт з тим самим HTTP/2 з'єднанням, що й HTML, стартує на 150–400 мс раніше, ніж Google Fonts на mobile 4G. Плюс self-hosting дає вам контроль над Cache-Control (наприклад, max-age=31536000, immutable) і можливість подавати з CDN, що вже поруч із користувачем.

Мінімальний робочий приклад self-hosted шрифту з правильним @font-face:

/* /assets/fonts/inter.css */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;           /* variable font: діапазон ваг */
  font-display: swap;
  src: url('/fonts/Inter-VF.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0400-04FF, U+2000-206F;
}

body {
  font-family: 'Inter', -apple-system, system-ui, sans-serif;
}

Як обрати font-display: swap, fallback чи optional

Дескриптор font-display керує тим, як браузер поводиться, поки шрифт завантажується. Це один із найбільш неправильно вживаних інструментів у web-performance. Багато джерел рекомендують «завжди swap», але це не оптимально для сайтів, де візуальна стабільність важливіша за миттєвий текст.

Порівняння значень, які реально мають сенс у 2026:

ЗначенняПеріод блокуванняПеріод swapКоли використовувати
auto~3 с (Chrome)НескінченнийНіколи, це FOIT за замовчуванням
swap0 мсНескінченнийОсновний текст, коли текст важливіший за візуал
fallback100 мс3 сBody-copy на новинних сайтах
optional100 мс0 мсДекоративні шрифти на slow-network

У моїй практиці для більшості продуктових сайтів найкраще працює комбінація swap + size-adjust: текст видно одразу, а завдяки підганяним метрикам fallback-шрифту немає видимого стрибка. Для декоративних заголовків, які завантажуються рідко, optional є розумним вибором: користувачам на 3G взагалі не покажуть цей шрифт, і це нормально.

Як правильно preload критичні шрифти

<link rel="preload"> для шрифтів є потужною, але легко зіпсовною оптимізацією. Правильно застосований preload прискорює появу тексту на 200–500 мс. Неправильно застосований витягує з мережі ресурси, що конкурують за пропускну здатність із LCP-зображенням, і робить LCP гіршим.

Так, я хапала цей баг сама, коли клієнт preload-ив шість шрифтів «на всяк випадок». Три правила, які я тепер застосовую у польових аудитах:

  1. Preload лише 1–2 шрифти, які точно потрібні above-the-fold (наприклад, лише regular-вагу для h1 і body).
  2. Використовуйте атрибут crossorigin навіть для same-origin шрифтів. Без нього браузер створить два окремих запити.
  3. Не preload варіанти, які не рендеряться одразу (bold, italic, декоративні заголовки нижче фолду).
<link
  rel="preload"
  href="/fonts/Inter-VF.woff2"
  as="font"
  type="font/woff2"
  crossorigin
  fetchpriority="high"
>

Атрибут fetchpriority="high" (Chrome 102+, Safari 17.2+) явно підвищує пріоритет запиту в мережевій черзі. Це особливо важливо, коли LCP є текстом: без нього браузер може віддати пріоритет hero-зображенню, і текст пізніше з'явиться. Про fetchpriority для зображень я писала у гіді з оптимізації зображень у 2026 році; для шрифтів логіка симетрична.

Як зменшити CLS від шрифтів через size-adjust

Це найважливіший розділ і найменш зрозумілий інструмент 2024–2026 років. size-adjust, ascent-override, descent-override та line-gap-override, це CSS-дескриптори, які дозволяють «підігнати» метрики fallback-шрифту так, щоб він займав рівно ту саму висоту й ширину рядка, що й web-шрифт. Коли web-шрифт нарешті завантажується, swap стається без layout shift.

Chrome DevTools у 2026 вміє автоматично генерувати ці значення (Rendering → «Emulate CSS media feature» → font-metrics generator). Але можна порахувати вручну:

/* Локальний fallback-родич системного шрифту */
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107.6%;        /* Arial x-height = 92.9% Inter → 100/92.9 */
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

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

Обов'язково перевірте цю комбінацію на реальному пристрої, а не тільки у DevTools, бо рендер шрифтів відрізняється між ОС. У моїх RUM-даних правильно налаштований fallback знижує CLS-p75 на 0.05–0.15 залежно від того, скільки тексту у viewport.

Для Next.js 13+ і Nuxt 3+ ця робота вже автоматизована через next/font та @nuxt/fonts: вони самостійно вимірюють метрики та генерують fallback. Але для власних платформ (WordPress, Astro, ванільний HTML) доведеться робити руками або через Capsize від Seek.

Variable fonts і WOFF2: одна вага замість шести

Variable font — це один файл, що містить неперервний спектр варіацій (вага 100–900, ширина, нахил) замість 4–8 окремих файлів. У WOFF2-форматі економія ваги суттєва: типовий Inter Variable у форматі WOFF2 важить ~135 КБ і покриває всі ваги від Thin до Black. Еквівалентний набір з 6 статичних файлів важить приблизно 380 КБ.

Синтаксис @font-face для variable font відрізняється діапазоном у font-weight:

@font-face {
  font-family: 'Inter';
  font-weight: 100 900;                     /* діапазон, не одне значення */
  font-style: normal;
  font-display: swap;
  src: url('/fonts/InterVariable.woff2') format('woff2');
}

h1 { font-weight: 750; }   /* довільне значення в діапазоні */
p  { font-weight: 425; }

Один нюанс, який я бачила у продакшні: якщо ви завантажуєте variable font, але у CSS використовуєте тільки font-weight: 400, це надлишок, і статичний файл буде легшим. Variable виправдано, коли ви реально використовуєте 3+ ваги.

WOFF2 у 2026 має 98.7% глобальної підтримки за Can I Use. Формати WOFF (v1), TTF, OTF і EOT можна безпечно прибрати з CSS, а це чиста економія HTML-байтів. Виняток: якщо у вас RUM-дані показують >1% трафіку на legacy-браузерах (IE, старий Android WebView).

Сабсетинг та unicode-range для української

Стандартний файл Inter містить ~2400 гліфів: латиниця, кирилиця, грецька, в'єтнамська, знаки пунктуації, IPA. Для українського сайту потрібно приблизно 400 гліфів (базова латиниця плюс кириличний блок плюс пунктуація). Сабсетинг є процесом видалення непотрібних гліфів, що зменшує файл на 60–90%.

Інструмент за замовчуванням: pyftsubset з пакета fonttools:

# Встановлення
pip install fonttools brotli

# Сабсет для української локалі
pyftsubset InterVariable.ttf \
  --output-file=InterVariable-uk.woff2 \
  --flavor=woff2 \
  --layout-features='*' \
  --unicodes="U+0000-00FF,U+0400-04FF,U+2000-206F,U+2070-209F,U+20A0-20CF"

# Перевірити результат
ls -lh InterVariable-uk.woff2

Діапазони кодових точок: U+0000-00FF покриває базову латиницю та Latin-1 Supplement; U+0400-04FF це кирилиця; U+2000-206F це загальна пунктуація (тире, лапки); U+2070-209F це надрядкові й підрядкові; U+20A0-20CF це символи валют (₴, €, $).

У CSS unicode-range дозволяє браузеру завантажити файл тільки якщо на сторінці зустрічається символ із діапазону. Це критично для мультимовних сайтів:

@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF;
}
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-cyrillic.woff2') format('woff2');
  unicode-range: U+0400-04FF;
}

Аудит шрифтів через RUM та CrUX

Синтетичний Lighthouse покаже вам «Ensure text remains visible during webfont load», але не скаже, чи ваша база користувачів реально страждає. Для цього потрібні польові дані.

Мій робочий стек 2026 року:

  1. PerformanceObserver + web-vitals.js: збирайте CLS-джерела у RUM. У web-vitals 4.x подія onCLS повертає entries[].sources[] з посиланням на DOM-вузли-порушники. Якщо серед них часто зустрічаються заголовки чи параграфи, це font-CLS.
  2. CrUX Dashboard у Looker Studio покаже 28-денний тренд CLS-p75 на mobile проти desktop. Різкі стрибки після релізу часто виникають через новий шрифт без preload.
  3. Chrome DevTools → Performance → Fonts (панель у DevTools 122+): таймлайн завантаження, парсингу й застосування шрифтів на конкретній сесії.

Стандартний фрагмент для збору font-CLS у RUM:

import { onCLS } from 'web-vitals';

onCLS((metric) => {
  const fontSources = metric.entries
    .flatMap(e => e.sources || [])
    .filter(s => s.node?.tagName?.match(/^(H\d|P|SPAN)$/i));

  if (fontSources.length > 0) {
    navigator.sendBeacon('/rum', JSON.stringify({
      metric: 'CLS',
      value: metric.value,
      likelyFont: true,
      nodes: fontSources.map(s => s.node?.tagName)
    }));
  }
});

Ця телеметрія за тиждень скаже вам чесно, чи допомогло size-adjust, чи ні. Синтетика цього не покаже, бо вона рендерить сторінку у стерильному середовищі, де шрифт часто вже в кеші.

Для розуміння того, як font-оптимізація вписується у більш широку картину швидкості рендерингу, подивіться повний гід із оптимізації LCP у 2026 році та офіційні рекомендації web.dev щодо font best practices.

Часті питання

Чи потрібен font-display: swap у 2026?

Так, для основного тексту, бо це усуває FOIT. Але swap без size-adjust провокує видимий CLS у момент підміни шрифту. Використовуйте обидва інструменти разом або замінюйте на optional для декоративних елементів.

Що краще: self-host шрифти чи Google Fonts?

Self-hosting майже завжди швидший після того, як Chrome ізолював HTTP-кеш за origin у 2020. Ви економите DNS, TLS і один redirect, а також отримуєте контроль над Cache-Control та CDN-роздачею.

Скільки шрифтів можна безпечно preload?

Не більше 1–2 файлів above-the-fold. Кожен preload забирає пропускну здатність у LCP-зображення. Preload лише regular-вагу для h1/body; bold і italic хай вантажаться природно.

Як зменшити CLS від шрифтів у Next.js?

Використовуйте next/font/google або next/font/local: вони автоматично генерують size-adjust, ascent-override та інші метрики для fallback-шрифту. Для WordPress або Astro застосовуйте Capsize або задавайте метрики вручну.

Чи впливають шрифти на INP?

Опосередковано. Підміна шрифту після завантаження запускає reflow тексту та repaint, що може блокувати головний потік на 5–20 мс на слабких пристроях. Правильні fallback-метрики зводять цей вплив до нуля, бо reflow стає непомітним.

Nadia El-Sayed
Про Автора Nadia El-Sayed

Core Web Vitals specialist focused on real-user monitoring. Believes synthetic-only perf testing is a comforting lie.