Bcorrections

Веб-шрифты и производительность: как не убить LCP кастомным фонтом

Кастомные шрифты — главный виновник провалов CLS и LCP. Разбираем стратегии font-display, preload, self-hosting и font-loading API для премиальной типографики без потерь.

Каждый дизайнер хочет премиальную типографику. Каждый разработчик подключает Google Fonts через <link>. И вот ваш сайт грузится отлично — все картинки, JS, CSS — а пользователь смотрит на белый экран ещё две секунды, потому что ждёт шрифт. LCP в красной зоне, Core Web Vitals провалены, позиции просели.

В этой статье — как использовать кастомные шрифты, не убивая производительность.

Как браузер обрабатывает шрифты

Стандартный процесс загрузки шрифта:

  1. Браузер парсит HTML, строит DOM
  2. Парсит CSS, находит @font-face или <link rel="stylesheet"> со шрифтом
  3. Только теперь запрашивает файл шрифта
  4. Скачивает шрифт (50-200 КБ обычно)
  5. Парсит шрифт, рендерит текст

Между шагами 1 и 5 проходит время. На быстром соединении — 200-500 мс, на медленном — 2-4 секунды. И всё это время ваш текст либо невидим (FOIT — Flash Of Invisible Text), либо рендерится системным fallback и потом «прыгает» на кастомный (FOUT — Flash Of Unstyled Text).

FOIT убивает LCP — нет текста = нет LCP-кандидата. FOUT убивает CLS — текст «прыгает» из-за разной ширины fallback и кастомного шрифта.

Стратегия 1: font-display

CSS-свойство font-display управляет поведением при загрузке. Значения:

  • auto — браузер сам решает, обычно FOIT
  • block — невидимый текст пока шрифт не загрузится (3 сек), потом swap
  • swap — сразу системный fallback, потом swap на кастомный (создаёт CLS)
  • fallback — компромисс: 100 мс невидимости, потом swap, через 3 сек fix
  • optional — самое щадящее: 100 мс невидимости, потом fallback навсегда (если не успел)

Рекомендация: swap для большинства сайтов (лучший UX), optional если CLS критичен и допустимо иногда не показать кастомный шрифт.

@font-face {
  font-family: 'General Sans';
  src: url('/fonts/general-sans-medium.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}

Стратегия 2: preload критичных шрифтов

font-display: swap решает проблему FOIT, но FOUT остаётся — пользователь видит сначала системный шрифт, потом замену. На главном hero-блоке это раздражает.

Решение — preload критичных шрифтов:

<head>
  <link rel="preload"
        href="/fonts/general-sans-medium.woff2"
        as="font"
        type="font/woff2"
        crossorigin>
</head>

Что это делает: браузер начинает скачивать шрифт сразу после HTML, не дожидаясь парсинга CSS. Шрифт обычно доезжает до момента когда нужно рендерить текст — FOUT не возникает.

Внимание: preload должен быть crossorigin атрибут даже для self-hosted шрифтов. Без него браузер сделает второй запрос за шрифтом, и preload бесполезен.

Что preload-ить:

  • Все weights, которые используются above-the-fold (обычно 400 + 600 или 500 + 700)
  • НЕ preload-ить все варианты — это засрёт сетевой приоритет

Стратегия 3: self-hosting вместо Google Fonts

Google Fonts через <link> имеет три недостатка:

  1. Лишний DNS-запрос к fonts.googleapis.com, потом к fonts.gstatic.com — 2 connection setup'а
  2. Зависимость от внешнего сервиса — если Google Fonts падает (бывало), у вас падает шрифт
  3. GDPR/152-ФЗ риск — Google Fonts может логировать IP пользователей, это персональные данные

Self-hosting (положить файлы шрифтов на свой сервер) решает всё:

# Скачайте файлы с https://gwfh.mranftl.com/fonts (Google Fonts Helper)
# Положите в public/fonts/

В CSS:

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

Плюс HTTP/2 с вашего домена — один TCP, все шрифты по одному каналу.

Стратегия 4: variable fonts

Кастомный шрифт обычно поставляется во многих файлах — один per weight + per style. 12 файлов = 12 запросов = 12 загрузок.

Variable fonts — один файл, в котором закодированы все weight'ы. Браузер интерполирует между ними. Один файл 80 КБ вместо 12 файлов по 30 КБ = экономия и трафика, и количества запросов.

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

/* Использование */
h1 { font-weight: 700; }
p { font-weight: 400; }
.lead { font-weight: 500; }

Поддержка variable fonts — все современные браузеры. Доступно с 2018 года.

Стратегия 5: подгонка fallback под кастомный

FOUT (прыжок шрифта) можно уменьшить, если системный fallback имеет похожие пропорции с кастомным шрифтом.

CSS свойства для этого: size-adjust, ascent-override, descent-override, line-gap-override в @font-face.

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

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

Эффект: пока загружается Inter, текст рендерится Arial'ом с подогнанными пропорциями — выглядит почти как Inter. Когда настоящий Inter подгружается — swap происходит без визуального скачка.

Утилита для расчёта overrides: Fontkit или Capsize CSS generator.

Стратегия 6: subsetting

Полный файл шрифта содержит все символы — латиницу, кириллицу, греческий, азиатские иероглифы, символы математики, эмодзи. Если у вас русскоязычный сайт, вам нужны только латиница + кириллица + базовые символы. Это сократит файл с 200 КБ до 60-80 КБ.

# Через pyftsubset (Python):
pyftsubset Inter-Regular.ttf \
  --unicodes="U+0020-007E,U+0400-04FF,U+0500-052F,U+2000-206F,U+2070-209F,U+20A0-20CF" \
  --output-file=Inter-Regular-subset.woff2 \
  --flavor=woff2

Или через Google Fonts API уже даёт subset:

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap&subset=cyrillic-ext');

Что не нужно делать

  • Не подгружайте 10 weights, если используете 3. Каждый weight = +30-50 КБ.
  • Не подключайте кастомные шрифты к печатным версиям и e-mail. Это лишний расход.
  • Не используйте FontFace JS API без необходимости. В большинстве случаев font-display: swap + preload решают задачу декларативно.

Реальный пример

«До» оптимизации:

  • Google Fonts через <link>, 4 weight'а
  • LCP 3.8 сек на 4G мобильном
  • CLS 0.15 из-за FOUT

«После»:

  • Self-hosted Inter Variable woff2 (78 КБ), один файл
  • Preload одного варианта
  • size-adjust overrides для fallback
  • LCP 1.9 сек
  • CLS 0.04

Только шрифты дали −1.9 сек к LCP. Никаких других правок.

Резюме

Шрифты — невидимая часть сайта, которая может убить производительность. Простые правила: self-hosting, variable fonts, preload + font-display: swap, subsetting, fallback с overrides. Реализуется за несколько часов работы.

Если у вас сайт с кастомной типографикой и проблемами с LCP — напишите нам. Бесплатный аудит производительности за 2 рабочих дня.

Веб-разработка

Это часть нашей услуги Разработка сайтов

Сайты под ключ, сразу готовые к SEO и AI-поиску

Перейти к услуге →
Идём дальше?

Нужна пара экспертных глаз на ваш проект?

Делаем экспресс-аудит за 2 рабочих дня: показываем где сайт теряет трафик и что исправить в первую очередь.

Обсудить проект