Веб-шрифты и производительность: как не убить LCP кастомным фонтом
Кастомные шрифты — главный виновник провалов CLS и LCP. Разбираем стратегии font-display, preload, self-hosting и font-loading API для премиальной типографики без потерь.
Каждый дизайнер хочет премиальную типографику. Каждый разработчик подключает Google Fonts через <link>. И вот ваш сайт грузится отлично — все картинки, JS, CSS — а пользователь смотрит на белый экран ещё две секунды, потому что ждёт шрифт. LCP в красной зоне, Core Web Vitals провалены, позиции просели.
В этой статье — как использовать кастомные шрифты, не убивая производительность.
Как браузер обрабатывает шрифты
Стандартный процесс загрузки шрифта:
- Браузер парсит HTML, строит DOM
- Парсит CSS, находит
@font-faceили<link rel="stylesheet">со шрифтом - Только теперь запрашивает файл шрифта
- Скачивает шрифт (50-200 КБ обычно)
- Парсит шрифт, рендерит текст
Между шагами 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— браузер сам решает, обычно FOITblock— невидимый текст пока шрифт не загрузится (3 сек), потом swapswap— сразу системный fallback, потом swap на кастомный (создаёт CLS)fallback— компромисс: 100 мс невидимости, потом swap, через 3 сек fixoptional— самое щадящее: 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> имеет три недостатка:
- Лишний DNS-запрос к
fonts.googleapis.com, потом кfonts.gstatic.com— 2 connection setup'а - Зависимость от внешнего сервиса — если Google Fonts падает (бывало), у вас падает шрифт
- 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 рабочих дня.