Все материалы

Performance · Geist · HTTP/2

Почему Geist Mono вызывает повторный Layout: поднаборы и preload

Автор: Grundy · Проверено: 25 октября 2026

Загрузка моноширинного шрифта неожиданно затрагивала заголовок главной и обычный текст. Мы проследили причину, проверили выделение редких поднаборов Geist в отдельное семейство и выяснили, почему Latin и Cyrillic как будто скачивались дважды. Область шрифтовой инвалидации сузилась, но полный LCP не показал устойчивого улучшения. Это отдельные лабораторные исследования при HTTP/2 и CPU ×4, а не отчёты PageSpeed или внедрённое исправление загрузки шрифтов.

1. Почему загрузка Geist Mono затрагивает обычный текст

В исследовании 23 октября изолировали загрузку Mono: стартовый внешний JS не скачивался, а только ответ Mono задерживали на 3000 мс. Даже когда основной Geist уже загружен, уведомление Mono помечало 119 уникальных узлов с причиной Fonts changed, включая H1. Contain: layout style у примера этого не остановил. По исходникам точной ревизии Blink из тестового Chrome цепочка такова: уведомление kFontFaceLoaded не ограничено семейством, а FontFallbackMap инвалидирует списки с HasLoadingFallback(). Объявленные, но не потребовавшиеся unicode-поднаборы могут сохранять этот признак у основного Geist. Это объясняет область пересчёта; Mono не подменяет шрифт заголовка.

Отдельный Mono: четыре запуска на вариант
МетрикаВсе поднаборы GeistТолько Latin + Cyrillic
Уникальные узлы Fonts changed1196
Dirty layout objects13122
H1 инвалидирован, запусков4 / 40 / 4
Медиана Layout Mono, мс33,221,0
Layout Mono min–max, мс26,6–33,816,3–24,4
Медиана LCP страницы, мс2 5242 678
Медиана Layout за 6 с, мс897,6946,2

В таблице — отдельный контроль из восьми загрузок, где у основного Geist оставлены только используемые Latin и Cyrillic; Mono и байты всех доставленных шрифтов не менялись. H1 исчез из поздней инвалидации во всех четырёх контролях. Минимальный документ без Next.js воспроизвёл изменение области: девять узлов превратились в два — PRE и его текст. Это подтверждение механизма широкой инвалидации, а не универсальная правка: удаление поднаборов теряет покрытие на других текстах. Более короткий проход Mono не улучшил полный LCP и суммарный Layout в этой малой серии.

В CSV сохранены все 32 загрузки: 16 начальных проверок, восемь контролей поднаборов, четыре проверки конечного вида и четыре минимального документа. В первых 16 дополнительный CDP-запрос шрифта после трассы завершился ошибкой устаревшего node ID; записанные трассы пригодны для проверки области инвалидации. Таблица использует исправленную последующую серию. В этих контролях конечная геометрия и выбранные шрифты совпали. Позже уточнили и область применения Mono: кроме PRE он оформляет маленькую подпись над H1; сам заголовок использует Geist.

Скачать 32 загрузки исследования Mono в CSV

2. Разделяем семейства без потери символов

Удаление редких поднаборов годится только для диагностики конкретного текста. Поэтому 24 октября проверили вариант с полным покрытием: Latin и Cyrillic оставили в Geist, три редких поднабора назвали Geist Extended и задали цепочку Geist, Geist Extended, Geist Fallback. Все 13 исходных font-face сохранены; у трёх меняется только имя семейства. Источники 11 WOFF2, unicode-range, веса 100–900, font-display и метрики остаются исходными. Стартовый JavaScript исполнялся в основной серии: 16 успешных холодных загрузок, по восемь на вариант, с чередованием порядка.

Стартовый JS исполняется: восемь запусков на вариант
ВариантМедиана LCP, мсLCP min–max, мсМедиана Layout за 6 с, мс
Исходные семейства2 7102 600–2 780921,7
Редкие поднаборы отдельно2 7142 660–2 904909,8

Устойчивого улучшения LCP нет. Медиана парных изменений — +28 мс для LCP и +18,3 мс для общего Layout; LCP улучшился в трёх из восьми пар. Разность групповых медиан не равна медиане парных изменений. Все завершённые медленные запуски сохранены. Отдельная серия механизма блокировала стартовый JS и задерживала Mono на 3000 мс: она проверяет область инвалидации, а не скорость обычной загрузки.

Изолированный Mono: четыре запуска на вариант
ВариантУзлы Fonts changedH1 инвалидирован, запусковМедиана Layout Mono, мсMono min–max, мс
Исходные семейства1194 / 432,930,3–41,4
Редкие поднаборы отдельно120 / 423,018,2–32,5

Проверка покрытия сохранила 604 кодовые точки Geist и 762 Geist Mono по пересечению объявленных unicode-range и фактического cmap. В 24 браузерных образцах, включая расширенную кириллицу, латиницу, вьетнамский, комбинируемые знаки и символы при весах 400, 500 и 700, совпали текст, геометрия и выбранные шрифты. Конечные снимки совпали попиксельно. Это не доказывает идентичность промежуточных кадров и всех сочетаний fallback. Среди оставшихся 12 инвалидированных узлов есть моноширинный текст и специальные символы; считать их все лишними нельзя.

Одна попытка серии механизма завершилась Navigating frame was detached и повторена; медленный повтор с LCP 3864 мс остался в данных. Обычная JS-серия прошла без повторов. Доставка с отключённым кешем и no-store давала по пять шрифтовых ответов в обоих обычных вариантах; следующая проверка объясняет это ограничение. Разделение сохранило покрытие и сузило поздний проход, но измеренный LCP не оправдывает внедрение ради скорости. Это лабораторное преобразование CSS, а не выполненная правка Next.js.

Скачать все 24 завершённых замера разделения семейств в CSV

3. Повторный preload не означает повторное применение шрифта

В серии с разделением семейств после старта JS было пять шрифтовых ответов вместо трёх. Отдельное исследование 24 октября установило источник: ранние Latin и Cyrillic запрашиваются через HTTP-заголовок Link, а позже декодер React Flight обрабатывает две записи HL и React DOM добавляет соответствующие link rel=preload в DOM. Ранние HTTP-подсказки не создали DOM-элементов, которые мог бы найти клиентский реестр. Стек инициатора и наблюдение DOM подтвердили этот путь в зафиксированном опубликованном бандле. Но добавление подсказки само по себе ещё не доказывает новую передачу по сети.

Кеш и Flight-подсказки: четыре завершённых запуска на режим
РежимПоздние DOM-подсказкиОтветы шрифтовМедиана LCP, мсLCP min–max, мс
Кеш выключен, no-store252 6442 524–2 892
Кеш включён, no-store23 или 5 (по два запуска)3 0442 604–3 892
Кеш включён, immutable232 7282 496–3 816
Лаборатория без двух Flight HL032 7062 452–2 936

Каждый режим использовал новый Chrome и приватный контекст, те же девять стартовых скриптов, ранний HTTP preload, байты шрифтов, HTTP/2, Slow 4G и CPU ×4. Холодный первый переход не требует запрещать повторное использование ресурсов внутри этой загрузки. Только диагностический вариант без подсказок удалял две записи Flight HL; это не предлагаемая правка сгенерированного HTML сайта. Общий Layout считали за одинаковые шесть секунд. Два сбоя запуска Chrome сохранены и повторены после основной серии, что изменило порядок; оставшийся от неудачного старта процесс дополнительно ограничивает сравнение малых разниц CPU-времени.

Лишние передачи Latin и Cyrillic добавляли 44 188 байт (43,2 КиБ) в лаборатории с выключенным кешем. Во всех 16 трассах у каждого файла было не более одного RemoteFontLoaded: ещё один ответ preload не применял CSS-шрифт повторно. В лабораторном режиме подсказки появлялись через 704–816 мс после LCP, а после 3,5 секунды весь Layout занимал лишь 0,84–1,46 мс. Удаление подсказок не дало устойчивого выигрыша LCP: парная медиана −20 мс, диапазон −276…+412 мс. Один immutable-запуск всё равно дал LCP 3816 мс при трёх ответах. Эти передачи не объясняют всю задержку LCP.

Отдельная проверка опубликованной главной в свежем приватном Chrome при CPU ×4, с обычным кешем, без сетевого эмулятора и подмены ресурсов получила три запроса/ответа шрифтов по HTTP/2. Все — с public,max-age=31536000,immutable. Оба поздних DOM-preload появились, повторных передач и ошибок страницы не было. Аналитические адреса блокировались. Это одна production-QA-проверка, а не сопоставимый замер скорости и не утверждение обо всех браузерах. Сохраняем действующие immutable-заголовки и ранний preload; дальнейшие сравнения пользовательской загрузки проводим в холодном контексте с обычным кешированием. Оптимизации загрузки шрифтов из этих трёх исследований на сайт не внедрены.

Скачать все 16 завершённых замеров preload в CSV