Акция Панель управления ispmanager для VDS — первый месяц бесплатно
до 31.07.2026 Подробнее
Выберите продукт

Шрифты без боли: preload, CORS и font-display для быстрой и стабильной загрузки

Шрифты — капризный ресурс фронтенда. Разберём быстрый и безопасный подход: preload, CORS, Cache-Control и font-display; как избежать двойных загрузок; дадим готовые конфиги Nginx/Apache и чек‑лист отладки.
Шрифты без боли: preload, CORS и font-display для быстрой и стабильной загрузки

В веб‑производительности нет «серебряной пули», но грамотная работа со шрифтами даёт очень осязаемый выигрыш. Шрифты часто грузятся с высокой приоритетностью, попадают под политику cross-origin, создают FOIT/FOUT эффекты, а ещё просыпаются через месяц с протухшим кэшем. В этой статье разложим по полочкам связку fonts + preload + CORS + Cache-Control + font-display и покажем устойчивые конфигурации для Nginx и Apache. Если вам нужен полный root‑доступ для тонкой настройки Nginx/Apache, возьмите облачный VDS.

Как браузер грузит шрифт и почему тут важны CORS, preload и cache-control

Большинство проектов используют WOFF2 как основной формат шрифтов: он сжат, поддерживается современными браузерами и даёт лучшую performance. Браузер встречает в CSS правило @font-face, сопоставляет его со стилями на странице и решает, когда запросить файл. Момент запроса критичен: если шрифт нужен «над фолдом», задержка даёт заметное моргание текста (FOUT) или задержку отображения (FOIT).

Три ключа к скорости и стабильности:

  • preload — подсказка браузеру «нужно это прямо сейчас». Работает, только если as="font" и указан корректный crossorigin для cross-origin сценариев.
  • CORS — шрифты относятся к «чувствительным» ресурсам; при загрузке с другого источника без корректных заголовков получим блокировку либо «таинственные» повторные запросы.
  • Cache-Control — долгий кеш с immutable и версионирование в имени файла предотвращают лишние сетевые обращения и «случайные» 304.

Если вы видите в консоли предупреждения вроде «The resource was preloaded but not used» или «No 'Access-Control-Allow-Origin' header», значит связка preload + CORS + @font-face настроена несимметрично.

Preload шрифтов без двойной загрузки

Минимальный и правильный пример для WOFF2:

<link rel="preload" as="font" type="font/woff2" href="/fonts/Inter-roman.var.woff2" crossorigin>

Ключевые детали:

  • as="font" — чтобы браузер назначил верный приоритет и использовал правильный кеш.
  • type="font/woff2" — помогает браузеру быстрее подтвердить тип контента.
  • crossorigin — обязательно, если файл шрифта загружается с иного origin или если на шрифт будут распространяться CORS‑ограничения (например, при строгих заголовках безопасности). Для шрифтов почти всегда достаточно анонимного режима (без куки и кредов), то есть просто crossorigin или crossorigin="anonymous".

Чтобы избежать double fetch (двойной запрос), URL в preload должен совпадать с URL в @font-face побайтно: без различий в регистре, без дополнительных query‑параметров и с тем же набором CORS‑атрибутов. Если CSS грузит шрифт с crossorigin, а preload — без него, браузер посчитает это разными запросами.

Связка с @font-face

@font-face {
  font-family: "Inter";
  src: url("/fonts/Inter-roman.var.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

Совет: если используете несколько начертаний или сабсеты, делайте отдельные preload только для тех файлов, которые действительно нужны «над фолдом». Остальные пускай грузятся лениво по факту применения CSS.

Пример настроек Nginx для CORS и кэширования шрифтов

font-display: как перестать бояться FOIT/FOUT

font-display управляет поведением текста, пока шрифт не загружен:

  • swap — показывает системный шрифт сразу, после загрузки подменяет на кастомный. Лучший выбор для длинного текста, максимальная читабельность и отзывчивость.
  • fallback — короткая фаза блокировки (обычно до 100 мс), затем показ фолбэка, затем подмена. Компромисс между «не мигать» и «сохранить метрики».
  • optional — браузер может вообще не грузить шрифт при неблагоприятной сети. Хорошо для второстепенных шрифтов или когда метрики очень чувствительны.
  • block — блокировка на длительное время. Сегодня почти не используется: риск «пустого» текста.
  • auto — по умолчанию, непредсказуемо для UX.

Практика для текста контента — swap или optional (если метрики и дизайн выдерживают). Для иконок‑шрифтов чаще важнее отсутствие «ломающихся» значков, поэтому многие выбирают fallback. И всё же иконки лучше выносить в SVG.

CORS для шрифтов: почему без него всё ломается

Шрифты подпадают под политику cross-origin. Если вы раздаёте их с иного домена или субдомена (например, со статического хоста или CDN), браузеру нужны корректные заголовки CORS, иначе запросы будут блокироваться или повторяться иной политикой. В большинстве случаев достаточно выдать:

  • Access-Control-Allow-Origin: конкретный origin, запрашивающий ресурс;
  • Vary: Origin: чтобы кеш корректно различал версии для разных источников;
  • при необходимости — Cross-Origin-Resource-Policy для согласования с политикой изоляции (если включали строгие заголовки безопасности).

Не используйте Access-Control-Allow-Origin: * совместно с куки/креденшелами. Шрифтам куки не нужны — оставляйте запрос анонимным и не добавляйте Access-Control-Allow-Credentials.

Nginx: CORS только для шрифтов и только для нужных сайтов

map $http_origin $cors_allowed {
    default 0;
    ~*^https?://(www\.)?example\.com$ 1;
    ~*^https?://static\.example\.com$ 1;
}

server {
    # ... остальная конфигурация

    location ~* \.(woff2|woff|ttf)$ {
        # Типы на всякий случай (обычно уже настроены глобально)
        types { font/woff2 woff2; font/woff woff; application/font-sfnt ttf; }

        if ($cors_allowed) {
            add_header Access-Control-Allow-Origin $http_origin always;
            add_header Vary "Origin" always;
            # Если используете строгую изоляцию, можно явно разрешить кросс-доступ
            add_header Cross-Origin-Resource-Policy "cross-origin" always;
        }

        # Долгий кеш для версионированных файлов
        add_header Cache-Control "public, max-age=31536000, immutable" always;
        expires 1y;
    }
}

Значение $http_origin отражается только для шрифтов, и только если домен входит в белый список. Это снижает риски, связанные с «отражением» Origin.

Apache: CORS и кеш для WOFF2

<IfModule mod_mime.c>
  AddType font/woff2 .woff2
  AddType font/woff .woff
  AddType application/font-sfnt .ttf
</IfModule>

<IfModule mod_headers.c>
  <FilesMatch "\.(woff2|woff|ttf)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>

  # Разрешаем CORS только для своих доменов
  SetEnvIfNoCase Origin "^https?://(www\.)?example\.com$" ORIGIN_OK=$0
  SetEnvIfNoCase Origin "^https?://static\.example\.com$" ORIGIN_OK=$0

  <FilesMatch "\.(woff2|woff|ttf)$">
    Header set Vary "Origin"
    Header set Access-Control-Allow-Origin "%{ORIGIN_OK}e" env=ORIGIN_OK
    Header set Cross-Origin-Resource-Policy "cross-origin"
  </FilesMatch>
</IfModule>

Вариант с отражением Origin безопасен, когда применяется только к статике шрифтов и у вас есть белый список доменов. Если раздаёте шрифты строго с того же домена, CORS не нужен вовсе. Для дополнительных паттернов загляните в шпаргалку по CORS‑заголовкам для Nginx и Apache.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Cache-Control для шрифтов: долго, неизменно и с версионированием

Шрифты — идеальные кандидаты для «долгого» кеша. Базовая стратегия:

  • Включаем Cache-Control: public, max-age=31536000, immutable для файлов с хешем в имени: Inter-roman.var.06b0f1.woff2.
  • При изменении файла — меняем имя хеша, и браузер качает новый вариант без конфликтов с кешем.
  • Оставляем ETag — неплохо для CDN и ручной проверки, но при immutable он редко используется браузером.

Сжатие: WOFF2 уже сжат, поэтому gzip или brotli для него не применяются. Убедитесь, что ваш сервер не пытается компрессовать эти расширения — это только тратит CPU и не меняет размер.

Nginx: исключаем сжатие и настраиваем заголовки

http {
    # ...
    gzip on;
    gzip_types text/css application/javascript;
    # .woff2 НЕ добавляем в gzip_types
}

server {
    location ~* \.(woff2|woff|ttf)$ {
        add_header Cache-Control "public, max-age=31536000, immutable" always;
        expires 1y;
    }
}

Apache: кеширование шрифтов

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType font/woff2 "access plus 1 year"
  ExpiresByType font/woff  "access plus 1 year"
  ExpiresByType application/font-sfnt "access plus 1 year"
</IfModule>

<IfModule mod_headers.c>
  <FilesMatch "\.(woff2|woff|ttf)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>
</IfModule>

Чтобы избежать предупреждений о смешанном контенте и получить HTTP/2/3, держите шрифты на HTTPS. Если сертификата ещё нет — оформите SSL-сертификаты.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Preload через Link header и 103 Early Hints

Если трудно править HTML, можно подсказать preload заголовком на уровне сервера. Бонус: при поддержке Early Hints (код 103) браузер начнёт тянуть шрифт ещё до основного ответа.

location = / {
  add_header Link "</fonts/Inter-roman.var.woff2>; rel=preload; as=font; type=font/woff2; crossorigin" always;
  # ...
}

Убедитесь, что этот путь совпадает с @font-face, иначе снова будет двойная загрузка. Подробнее о приоритетах загрузки и Early Hints — в заметке про preload, приоритеты и кеш.

Как выбрать, что именно preload-ить

Preload — это высокий приоритет загрузки. Если переборщить, забьёте канал и ухудшите TTFB/TTI. Выбираем минимум: основной романный шрифт для латиницы/кириллицы, используемый «над фолдом». Для остальных начертаний и скриптов полагайтесь на загрузку по требованию.

Хороший тон — разбить шрифт на сабсеты с unicode-range, чтобы браузер не тянул лишнее для страницы на одном языке.

@font-face {
  font-family: "InterSubset";
  src: url("/fonts/Inter-latin.woff2") format("woff2");
  unicode-range: U+000-5FF;
  font-display: swap;
}
@font-face {
  font-family: "InterSubset";
  src: url("/fonts/Inter-cyrillic.woff2") format("woff2");
  unicode-range: U+0400-04FF;
  font-display: swap;
}

В таком случае preload имеет смысл делать только для того сабсета, который реально встретится на первом экране. Этот подход уменьшает размер первой пачки данных и заметно улучшает performance на мобильной сети.

Типичные ошибки и как их избежать

  • Разные URL в preload и @font-face. Решение: унифицируйте путь и параметры, включая query‑строку.
  • Отсутствует crossorigin в preload при cross-origin раздаче. Решение: добавьте crossorigin и корректные CORS‑заголовки на сервере.
  • Шрифт с коротким кешем без immutable. Решение: версионируйте файл и увеличьте TTL.
  • Слишком много preload. Решение: ограничьтесь критичным шрифтом «над фолдом».
  • Gzip для WOFF2. Решение: исключите расширение из компрессии.
  • Строгие заголовки безопасности блокируют кросс‑ресурсы. Если используете изоляцию, проверьте Cross-Origin-Resource-Policy и совместимость с CORS.

Отладка: что смотреть в DevTools и через curl

В DevTools обратите внимание на:

  • Initiator: у прелоада будет «link preload», у обычной загрузки — CSS.
  • Priority: после preload должен быть высокий приоритет.
  • Headers: убедитесь, что content-type корректный, есть Cache-Control, при cross-origin виден Access-Control-Allow-Origin и Vary: Origin.
  • Waterfall: нет ли двойной загрузки одного и того же файла.

Проверка CORS и кеша из консоли:

# Эмуляция cross-origin запроса (Origin заголовок)
curl -I -H "Origin: https://www.example.com" https://static.example.com/fonts/Inter-roman.var.woff2

# Проверка, что сервер не отдает лишние заголовки и тип корректный
curl -I https://site.example.com/fonts/Inter-roman.var.woff2

Вы должны увидеть для cross-origin запросов: HTTP/2 200, content-type: font/woff2, access-control-allow-origin: https://www.example.com, vary: Origin, cache-control: public, max-age=31536000, immutable.

DevTools с отображением приоритетов и источников загрузки шрифтов

И ещё немного практики: метрики, приоритеты и пре-коннекты

Preload повышает приоритет сетевого запроса. Если шрифтов несколько, а канал ограничен, это может задержать HTML, CSS или критический JS. Поэтому вместо «preload всего» используйте «preload ровно одного действительно критичного шрифта». Остальные отдавайте через @font-face с font-display: swap.

Для шрифтов с другого домена иногда полезно «раскочегарить» соединение заранее. В ряде случаев помогает preconnect к статическому домену — он установит TCP/TLS раньше. Но помните: preconnect сам по себе не заменяет preload и CORS; это лишь оптимизация рукопожатия. Применяйте точечно, только для доменов, которые действительно участвуют в критическом пути.

Чек-лист внедрения

  1. Определите, какой шрифт реально нужен «над фолдом» и есть ли смысл в сабсетах через unicode-range.
  2. Добавьте preload для одного критичного WOFF2 с as="font", type="font/woff2" и crossorigin при необходимости.
  3. Синхронизируйте URL и параметры между preload и @font-face.
  4. Настройте CORS на сервере, если шрифт грузится cross-origin: Access-Control-Allow-Origin и Vary: Origin.
  5. Включите Cache-Control: public, max-age=31536000, immutable для версионированных файлов.
  6. Проверьте типы (font/woff2) и отключите лишнюю компрессию для WOFF2.
  7. Выберите font-display по контенту: текст — swap или optional, иконки — подумайте о замене на SVG.
  8. Отладьте в DevTools и через curl; устраните предупреждения «preloaded but not used» и CORS‑ошибки.

Продвинутые нюансы

Variable fonts. Переменные шрифты удобны тем, что заменяют набор начертаний одним файлом. Но они крупнее. Не всегда стоит делать preload всего variable‑шрифта — возможно, выгоднее сабсетить и грузить остальные оси лениво.

FOFT (Flash of Faux Text). Использование метрик‑совместимых системных шрифтов как фолбэка уменьшает скачки макета при подмене. В паре с font-display: swap это даёт ровный UX.

Early Hints и CDN. Если перед фронтом стоит CDN с поддержкой 103, вынесите Link: preload на край и дайте шрифту фору. При этом следите, чтобы правила CORS были на месте на том же краю.

Политики безопасности. Если включали строгие заголовки (например, для изоляции контента), оцените влияние Cross-Origin-Resource-Policy на шрифты. Для cross-origin загрузки может потребоваться значение cross-origin.

Итоги

Чтобы шрифты работали быстро и предсказуемо, достаточно собрать вместе четыре кирпича: аккуратный preload, корректный CORS, долгий Cache-Control и уместный font-display. Добавьте к этому дисциплину в путях и параметрах, исключите лишнюю компрессию WOFF2 — и получите стабильную загрузку без «миганий», повторных запросов и предупреждений браузера.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину

Ошибка mount: wrong fs type, bad option, bad superblock в Debian/Ubuntu может означать и простую опечатку в имени раздела, и пробл ...
Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление

Если XFS-раздел внезапно стал доступен только для чтения, а сервер ушёл в emergency mode, главное — не спешить. Разберём безопасны ...
Debian/Ubuntu: как исправить Failed to fetch при apt update OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Failed to fetch при apt update

Ошибка Failed to fetch при apt update в Debian и Ubuntu обычно связана не с самим APT, а с DNS, сетью, зеркалом, прокси, временем ...