Google Fonts кажется удобным решением: одна строка кода и на сайте красивая типографика. Но за этим удобством скрываются три серьёзные проблемы: каждый шрифт добавляет 200-500 мс к времени загрузки (DNS-поиск + TLS-рукопожатие к googleapis.com), шрифты блокируют отрисовку первого контента (FCP), а с 2022 года использование Google Fonts без согласия пользователя нарушает GDPR (решение суда Мюнхена). Решение — self-hosting шрифтов с правильной стратегией загрузки через preload и font-display.
Почему Google Fonts — это проблема
При подключении Google Fonts происходит цепочка сетевых запросов, каждый из которых добавляет задержку:
- DNS-lookup для
fonts.googleapis.com(~50 мс) - TLS-рукопожатие (~100 мс)
- Загрузка CSS с описанием шрифтов (~50 мс)
- DNS + TLS для
fonts.gstatic.com(~150 мс) - Загрузка woff2-файлов (~100-300 мс)
Итого: 450-650 мс до того, как текст на странице станет читаемым. Для сайтов с Core Web Vitals целью LCP < 2.5с это критичная задержка. К тому же IP-адрес посетителя передаётся в Google, что в EU трактуется как передача персональных данных без согласия.
Шаг 1: Выбор и скачивание шрифтов
Для технического блога в стиле VS Code используются два шрифта: Inter (для основного текста) и JetBrains Mono (для кода и заголовков). Скачиваем их в формате woff2 — он даёт максимальное сжатие и поддерживается всеми современными браузерами.
Через Google Webfonts Helper:
Сервис gwfh.mranftl.com позволяет скачать Google Fonts в виде готовых woff2-файлов с CSS.
- Выбираем шрифт (например, Inter)
- Отмечаем нужные начертания: 400, 500, 600, 700
- Скачиваем ZIP-архив
- Распаковываем и переименовываем файлы для ясности
Структура файлов в теме:
wp-content/themes/customcode/
└── assets/
└── fonts/
├── inter-v13-latin_cyrillic-400.woff2
├── inter-v13-latin_cyrillic-500.woff2
├── inter-v13-latin_cyrillic-600.woff2
├── inter-v13-latin_cyrillic-700.woff2
├── jetbrains-mono-v18-latin_cyrillic-400.woff2
└── jetbrains-mono-v18-latin_cyrillic-500.woff2Важно скачивать версии с поддержкой кириллицы (latin_cyrillic), иначе русские буквы будут рендериться fallback-шрифтом.
Шаг 2: Подключение через @font-face в style.css
Добавляем правила @font-face в начало style.css. Ключевой параметр — font-display: swap, который предотвращает FOIT (Flash of Invisible Text) и снижает CLS.
/* Inter — основной шрифт */
@font-face {
font-family: 'Inter';
src: url('assets/fonts/inter-v13-latin_cyrillic-400.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+0400-045F, U+0490-0491;
}
@font-face {
font-family: 'Inter';
src: url('assets/fonts/inter-v13-latin_cyrillic-500.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+0400-045F, U+0490-0491;
}
@font-face {
font-family: 'Inter';
src: url('assets/fonts/inter-v13-latin_cyrillic-600.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+0400-045F, U+0490-0491;
}
@font-face {
font-family: 'Inter';
src: url('assets/fonts/inter-v13-latin_cyrillic-700.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+0400-045F, U+0490-0491;
}
/* JetBrains Mono — для кода */
@font-face {
font-family: 'JetBrains Mono';
src: url('assets/fonts/jetbrains-mono-v18-latin_cyrillic-400.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+0400-045F, U+0490-0491;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('assets/fonts/jetbrains-mono-v18-latin_cyrillic-500.woff2') format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+0400-045F, U+0490-0491;
}Разбор ключевых параметров:
- format(‘woff2’) — браузер сразу понимает формат, не тратит время на autodetect
- font-display: swap — текст отображается fallback-шрифтом до загрузки кастомного, нет FOIT
- unicode-range — браузер загружает шрифт только если на странице есть символы из этого диапазона. Экономит трафик для страниц без кириллицы
Шаг 3: Preload критических шрифтов
Даже с self-hosting шрифты начинают загружаться только после парсинга CSS. preload запускает загрузку параллельно с HTML, экономя 100-200 мс.
Добавляем в functions.php:
/**
* Preload критических шрифтов
*/
function customcode_preload_fonts() {
$fonts = [
get_template_directory_uri() . '/assets/fonts/inter-v13-latin_cyrillic-400.woff2',
get_template_directory_uri() . '/assets/fonts/inter-v13-latin_cyrillic-600.woff2',
get_template_directory_uri() . '/assets/fonts/jetbrains-mono-v18-latin_cyrillic-400.woff2',
];
foreach ( $fonts as $font ) {
printf(
'' . "\n",
esc_url( $font )
);
}
}
add_action( 'wp_head', 'customcode_preload_fonts', 1 );Прелоадятся только самые используемые начертания (Regular и Semibold для Inter, Regular для Mono). Bold и Medium подгружаются лениво — это экономит 30-40 КБ на первом экране.
Почему обязателен атрибут crossorigin:
Без crossorigin браузер загружает шрифт дважды — один раз для preload, второй раз при использовании в CSS. Это распространённая ошибка, которая удваивает трафик вместо экономии.
Шаг 4: font-size-adjust для стабильного CLS
Даже с font-display: swap происходит небольшая «дрожь» макета, когда fallback-шрифт (system-ui) заменяется на Inter — у них разный x-height. Параметр font-size-adjust выравнивает высоту строчных букв.
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size-adjust: 0.52; /* x-height Inter */
}
code, pre, .font-mono {
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-size-adjust: 0.51; /* x-height JetBrains Mono */
}Как подобрать значение font-size-adjust:
Формула: x-height / font-size. Для Inter при 100px font-size x-height ≈ 52px, значит значение 0.52. Современные браузеры (Chrome 120+) также поддерживают новое свойство font-metrics-override, но font-size-adjust работает везде.
Шаг 5: Правильный fallback-стек
Fallback-шрифты должны быть максимально похожи на кастомные по метрикам. Для Inter лучшим fallback является system-ui (San Francisco на macOS, Segoe UI на Windows, Roboto на Android).
:root {
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', Consolas, monospace;
}Порядок важен: сначала кастомный шрифт, затем системные шрифты ОС (они загружены мгновенно), затем универсальные семейства.
Шаг 6: Кэширование шрифтов на 1 год
Шрифты меняются редко, поэтому их можно кэшировать агрессивно. На shared-хостинге Бегет это делается через .htaccess:
# Долгое кэширование шрифтов
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType application/font-woff2 "access plus 1 year"
ExpiresByType application/x-font-woff "access plus 1 year"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.(woff2|woff)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule>На Nginx-серверах (VPS) соответствующий блок в конфиге сайта:
location ~* \.(woff2|woff)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}Директива immutable говорит браузеру не делать conditional-запросы (If-Modified-Since) — файл не изменится по этому URL.
Шаг 7: Отключение Google Fonts от плагинов и тем
Многие плагины (Elementor, Contact Form 7, Revolution Slider) и темы подключают Google Fonts самостоятельно. Эти подключения нужно отключить, иначе дублирование шрифтов сведёт на нет всю оптимизацию.
Универсальный код для отключения:
/**
* Отключение Google Fonts во всех плагинах и темах
*/
add_filter( 'style_loader_tag', function( $html, $handle ) {
if ( strpos( $html, 'fonts.googleapis.com' ) !== false ||
strpos( $html, 'fonts.gstatic.com' ) !== false ) {
return ''; // Полностью удаляем тег
}
return $html;
}, 10, 2 );
// Отключение Google Fonts в Elementor
add_action( 'elementor/frontend/after_enqueue_styles', function() {
wp_dequeue_style( 'elementor-google-fonts' );
wp_deregister_style( 'elementor-google-fonts' );
} );
// Отключение Google Fonts в Gutenberg
add_action( 'enqueue_block_assets', function() {
global $wp_styles;
if ( isset( $wp_styles->queue ) ) {
foreach ( $wp_styles->queue as $handle ) {
if ( strpos( $handle, 'font' ) !== false ) {
wp_dequeue_style( $handle );
}
}
}
} );Проверка через DevTools:
Откройте сайт, нажмите F12 → Network → фильтр font. Если в списке есть запросы к fonts.googleapis.com или fonts.gstatic.com — значит где-то осталось подключение Google Fonts. Ищите источник по имени handle в колонке Initiator.
Шаг 8: Subset-оптимизация для многоязычных сайтов
Если сайт на нескольких языках, можно разделить шрифт на subsets и подгружать только нужные. Например, для английских страниц не нужна кириллица:
/* Latin-only subset (для английских страниц) */
@font-face {
font-family: 'Inter';
src: url('assets/fonts/inter-v13-latin-400.woff2') format('woff2');
font-weight: 400;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}
/* Cyrillic subset (для русских страниц) */
@font-face {
font-family: 'Inter';
src: url('assets/fonts/inter-v13-cyrillic-400.woff2') format('woff2');
font-weight: 400;
font-display: swap;
unicode-range: U+0400-045F, U+0490-0491;
}Браузер сам выберет нужный файл на основе символов на странице. Это экономит 20-30 КБ на английских страницах.
Шаг 9: Variable Fonts (продвинутая оптимизация)
Variable Fonts — это один файл, содержащий все начертания (от 100 до 900 по весу). Inter Variable и JetBrains Mono доступны в таком формате, что сокращает количество HTTP-запросов с 4-6 до 1.
@font-face {
font-family: 'Inter Variable';
src: url('assets/fonts/Inter-Variable.woff2') format('woff2-variations');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
/* Использование любых весов из одного файла */
.text-light { font-weight: 300; }
.text-regular { font-weight: 400; }
.text-medium { font-weight: 500; }
.text-semibold { font-weight: 600; }
.text-bold { font-weight: 700; }Variable Font Inter (все веса) весит ~150 КБ, против ~120 КБ у четырёх отдельных woff2. Для сайтов с 3+ начертаниями variable fonts почти всегда выигрывают.
Шаг 10: Измерение результата
После всех оптимизаций проверяем метрики через несколько инструментов:
WebPageTest (детальный waterfall):
- Запускаем тест на webpagetest.org с локацией, близкой к основной аудитории
- Смотрим вкладку Waterfall: шрифты должны загружаться в первые 500 мс параллельно с HTML
- Проверяем, что нет запросов к
fonts.googleapis.com
Lighthouse / PageSpeed Insights:
- Секция «Avoid enormous network payloads» — шрифты должны быть < 100 КБ
- Секция «Preload Largest Contentful Paint element» — если LCP-элемент использует шрифт, он должен быть прелоажен
- CLS должен упасть ниже 0.05
DevTools Network:
# Ожидаемые заголовки ответа для шрифтов:
Content-Type: font/woff2
Cache-Control: public, max-age=31536000, immutable
Content-Encoding: br (или gzip)Чек-лист оптимизации шрифтов
- ✅ Google Fonts удалены из всех подключений
- ✅ Шрифты скачаны в woff2 формате с поддержкой кириллицы
- ✅ Файлы лежат в
assets/fonts/темы - ✅
@font-faceсfont-display: swapиunicode-range - ✅ Preload критических начертаний в
<head>сcrossorigin - ✅
font-size-adjustдля предотвращения CLS - ✅ Правильный fallback-стек (system-ui для sans, SF Mono для mono)
- ✅ Кэширование на 1 год с
immutable - ✅ Рассмотрен переход на Variable Fonts
- ✅ Метрики LCP и CLS улучшены
Решение частых проблем
Шрифт не загружается (в DevTools 404)
Проверьте путь в src: url(). Относительные пути в CSS считаются от местоположения самого CSS-файла, а не от корня сайта. Если style.css в корне темы, то путь assets/fonts/font.woff2 корректен. Если CSS минифицируется и перемещается — используйте абсолютные пути через get_template_directory_uri().
FOIT (Flash of Invisible Text) — текст невидим первые секунды
Значит, font-display установлен в block или auto. Замените на swap. В редких случаях (логотипы, брендинг) можно использовать optional — если шрифт не загрузился за 100 мс, используется fallback навсегда.
FOUT (Flash of Unstyled Text) — текст «прыгает» при загрузке
Это нормально для font-display: swap, но минимизируется через font-size-adjust и правильный fallback-стек. Если прыжки критичны для UX (например, в hero-секции), используйте inline-шрифт в Critical CSS для первого экрана.
CORS-ошибка при загрузке шрифтов с CDN
Шрифты требуют CORS-заголовков. Если шрифты отдаются с другого домена (CDN), добавьте на стороне CDN заголовок:
add_header 'Access-Control-Allow-Origin' '*';Для шрифтов * безопасен — они не содержат чувствительных данных.
Шрифт не отображается в Safari
Safari требователен к синтаксису @font-face. Убедитесь, что:
- Используется кавычки вокруг имени шрифта:
font-family: 'Inter'; - Указан
format('woff2'), а неformat("woff2")(двойные кавычки иногда не работают) - Нет опечаток в
unicode-range
Итог
Self-hosting шрифтов с preload и правильной стратегией font-display убирает 400-600 мс задержки от Google Fonts, решает проблему GDPR и значительно улучшает Core Web Vitals (особенно LCP и CLS). Вложения минимальны: 10 минут на скачивание woff2 и настройку CSS, а выгода — стабильно быстрая загрузка сайта на любом хостинге и независимость от внешних сервисов.