Оптимизация веб-шрифтов: цена каждого решения
Шрифты — вторая по весу категория ресурсов после картинок и главный источник сдвигов макета. Разбираем, чем платит страница за font-display, preload и подмножества.

Откуда берётся проблема
Один начертанием шрифт в формате WOFF2 — это 20–40 КБ. Семейство из четырёх начертаний (regular, italic, bold, bold-italic) — уже 80–160 КБ, которые браузер грузит по критическому пути. Пока файл не пришёл, у вас два варианта поведения текста: невидимый (FOIT) или подменённый системным (FOUT). Первый убивает First Contentful Paint, второй даёт сдвиг макета и портит Cumulative Layout Shift.
Здесь нет бесплатных решений. Каждый приём — font-display, preload, subsetting, self-hosting — что-то ускоряет и чем-то за это платит. Разбираем цену каждого на уровне метрик Core Web Vitals: LCP, CLS и время до первого текста. Речь о статике, отдаваемой через HTTP/2 или HTTP/3; про вариативные шрифты — отдельно в конце.
font-display: чем платит текст за скорость показа
Дескриптор font-display внутри @font-face управляет тем, что браузер делает, пока веб-шрифт грузится. Пять значений, и у каждого своя цена.
@font-face {
font-family: "Inter";
src: url("/fonts/inter-regular.woff2") format("woff2");
font-weight: 400;
font-display: swap;
}Что происходит при каждом значении:
- auto — на усмотрение браузера. В Chrome это фактически block с коротким периодом. Непредсказуемо, для продакшена не годится.
- block — текст невидим до ~3 секунд, пока ждём шрифт. Нет сдвига, но есть пустое место вместо контента. Худший LCP.
- swap — сразу системный шрифт, потом замена на веб-шрифт. Нулевой FOIT, но гарантированный сдвиг макета, когда придёт файл. Портит CLS.
- fallback — блок ~100 мс, затем системный; если шрифт пришёл в течение ~3 секунд, подменяет. Компромисс.
- optional — блок ~100 мс, и если шрифт не успел — он не применяется вовсе в этой загрузке. Нулевой CLS, но пользователь может так и не увидеть ваш шрифт на медленной сети.
swap делает страницу быстрой на бумаге и дёрганой на экране. optional делает её стабильной, но иногда — не вашим шрифтом. Выбор — это не «лучшее значение», а какой из двух дефектов вы готовы показать пользователю.
Как убрать сдвиг при swap
Сдвиг возникает, потому что системный fallback и веб-шрифт имеют разную ширину и высоту строки. Дескрипторы size-adjust, ascent-override, descent-override и line-gap-override позволяют подогнать метрики fallback-шрифта под веб-шрифт, чтобы момент замены не двигал текст.
/* Подгоняем системный Arial под метрики Inter, */
/* чтобы swap не сдвигал строки */
@font-face {
font-family: "Inter Fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}Проценты подбираются под конкретную пару шрифтов — их не угадать в уме. Инструмент для расчёта — генератор из проекта Fontaine или ручной замер через capsize. Значения выше — иллюстрация синтаксиса, а не готовые числа для Inter: проверяйте на своей паре. Поддержка ascent-override и соседних дескрипторов есть в актуальных Chrome, Firefox и Safari, но точную границу версий сверяйте в MDN.
preload: ускоряет обнаружение, крадёт полосу
Браузер узнаёт о шрифте только когда распарсит CSS и найдёт правило, которое реально применяется к видимому элементу. Это поздно: CSS сам грузится, парсится, потом строится дерево, потом стартует запрос шрифта. preload в <head> сдвигает старт загрузки к самому началу.
<link rel="preload" href="/fonts/inter-regular.woff2"
as="font" type="font/woff2" crossorigin>Атрибут crossorigin обязателен даже для своего домена — шрифты всегда запрашиваются в режиме CORS, и без него браузер сделает второй запрос, а первый уйдёт впустую.
Цена preload прямая: вы повышаете приоритет шрифта, а значит понижаете приоритет чего-то другого на том же соединении — картинок, скриптов, того же LCP-изображения. Предзагружать имеет смысл один-два начертания, которые точно нужны в первом экране. Предзагрузка всех четырёх начертаний семейства означает, что вы забили критический путь 150 КБ, часть которых нужна только ниже сгиба.
Subsetting: режем вес, теряем символы
Полный шрифт с латиницей, кириллицей, знаками и лигатурами весит в разы больше, чем нужно русскоязычному сайту. Subsetting оставляет только используемые диапазоны. Инструмент — fonttools (Python), команда pyftsubset.
# Оставляем латиницу и кириллицу, конвертируем в WOFF2
# fonttools 4.x
pyftsubset inter-regular.ttf \
--unicodes="U+0000-00FF,U+0400-04FF" \
--flavor=woff2 \
--output-file=inter-regular-subset.woff2Экономия реальна — с кириллического диапазона обычно 40–60% веса. Но цена в том, что вы отрезаете символы, которых нет в диапазоне. Появится имя с диакритикой из другого алфавита, знак валюты вне заданного набора или эмодзи в отзыве — браузер подставит системный глиф, и он будет выбиваться. Для сайта с пользовательским контентом режьте осторожнее, чем для статьи с фиксированным текстом.
Unicode-range: разбить на файлы
Дескриптор unicode-range в @font-face заставляет браузер грузить файл только если на странице встретился символ из диапазона. Кириллический подмножество не скачается на странице, где нет ни одной кириллической буквы.
@font-face {
font-family: "Inter";
src: url("/fonts/inter-cyrillic.woff2") format("woff2");
unicode-range: U+0400-04FF;
font-display: swap;
}Сравнение подходов и цена каждого
| Приём | Что выигрывает | Чем платит |
|---|---|---|
| font-display: swap | Нулевой FOIT, быстрый первый текст | Сдвиг макета, страдает CLS |
| font-display: optional | Нулевой CLS | На медленной сети шрифт не покажется |
| preload | Ранний старт загрузки, лучше LCP текста | Отнимает приоритет у других ресурсов |
| Subsetting | Меньше вес файла | Отрезанные символы падают на fallback |
| Метрики fallback (size-adjust) | Убирает сдвиг при swap | Ручной подбор под каждую пару шрифтов |
| Self-hosting вместо CDN | Одно соединение, контроль кеша | Нет общего кеша между сайтами, ваш трафик |
Когда что выбирать
- Основной текст статьи, фиксированный контент — subsetting по языку +
font-display: swap+ подогнанные метрики fallback. Сдвига не будет, вес минимальный. - Заголовок в первом экране — preload одного начертания, чтобы LCP-элемент отрисовался вашим шрифтом сразу.
- Декоративный или брендовый шрифт, без которого можно жить —
font-display: optional. Не увидят на 3G — не страшно. - Пользовательский контент с произвольными символами — subsetting осторожный или полный набор, иначе чужие имена и знаки поедут на системный шрифт.
Что ломается на практике
- preload без crossorigin — самый частый промах. Файл грузится дважды, оптимизация превращается в антиоптимизацию. Проверяется в Network: если один и тот же шрифт запрошен два раза, дело в этом атрибуте.
- preload файла, который не используется — консоль выдаст предупреждение «The resource was preloaded but not used». Обычно так бывает, когда предзагрузили начертание, а на странице оно не понадобилось.
- swap без подгонки метрик — CLS растёт скачком в момент замены шрифта. В Lighthouse это видно как красный CLS при зелёном FCP.
- Слишком агрессивный subsetting — тире и кавычки-ёлочки вылетают из диапазона, и в тексте они рисуются другим шрифтом. Добавьте типографские символы в набор явно.
Вариативные шрифты — отдельный расчёт
Один файл вариативного шрифта заменяет все начертания одного семейства и весит меньше, чем их сумма, но больше любого отдельного. Если вы используете два начертания из семейства — обычный статический набор легче. Если пять и больше, плюс промежуточные веса — вариативный выигрывает. Считайте по факту, а не по правилу.
Что делать дальше
Откройте вкладку Network в DevTools, отфильтруйте по Font и посмотрите: сколько файлов, какого веса, есть ли дубли, когда стартует запрос относительно начала загрузки. Затем прогоните страницу через Lighthouse и посмотрите на CLS и LCP по отдельности — они конфликтуют, и цель не «сделать оба зелёными любой ценой», а понять, каким дефектом вы готовы заплатить. Начните с subsetting по языку и подгонки метрик fallback — это даёт больше всего результата без риска. preload добавляйте точечно и проверяйте, что он не отнял приоритет у LCP-картинки.
Поиск источников: Реальные статьи и книги со ссылками, библиография по ГОСТ
Спросить за 15 ₽Источники: MDN — @font-face и font-display, web.dev — Best practices for fonts
Частые вопросы
У меня старый браузер в аудитории — сработают ли ascent-override и size-adjust?
Я добавил preload, а в консоли предупреждение «preloaded but not used». Что не так?
Что выбрать между swap и optional, если не уверен?
Subsetting сломает отображение, если пользователь введёт символ вне диапазона?
Стоит ли вообще уходить от Google Fonts CDN к self-hosting?
Вариативный шрифт всегда легче набора статических?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.