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 не подменяет шрифт заголовка.
| Метрика | Все поднаборы Geist | Только Latin + Cyrillic |
|---|---|---|
| Уникальные узлы Fonts changed | 119 | 6 |
| Dirty layout objects | 131 | 22 |
| H1 инвалидирован, запусков | 4 / 4 | 0 / 4 |
| Медиана Layout Mono, мс | 33,2 | 21,0 |
| Layout Mono min–max, мс | 26,6–33,8 | 16,3–24,4 |
| Медиана LCP страницы, мс | 2 524 | 2 678 |
| Медиана Layout за 6 с, мс | 897,6 | 946,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 в CSV2. Разделяем семейства без потери символов
Удаление редких поднаборов годится только для диагностики конкретного текста. Поэтому 24 октября проверили вариант с полным покрытием: Latin и Cyrillic оставили в Geist, три редких поднабора назвали Geist Extended и задали цепочку Geist, Geist Extended, Geist Fallback. Все 13 исходных font-face сохранены; у трёх меняется только имя семейства. Источники 11 WOFF2, unicode-range, веса 100–900, font-display и метрики остаются исходными. Стартовый JavaScript исполнялся в основной серии: 16 успешных холодных загрузок, по восемь на вариант, с чередованием порядка.
| Вариант | Медиана LCP, мс | LCP min–max, мс | Медиана Layout за 6 с, мс |
|---|---|---|---|
| Исходные семейства | 2 710 | 2 600–2 780 | 921,7 |
| Редкие поднаборы отдельно | 2 714 | 2 660–2 904 | 909,8 |
Устойчивого улучшения LCP нет. Медиана парных изменений — +28 мс для LCP и +18,3 мс для общего Layout; LCP улучшился в трёх из восьми пар. Разность групповых медиан не равна медиане парных изменений. Все завершённые медленные запуски сохранены. Отдельная серия механизма блокировала стартовый JS и задерживала Mono на 3000 мс: она проверяет область инвалидации, а не скорость обычной загрузки.
| Вариант | Узлы Fonts changed | H1 инвалидирован, запусков | Медиана Layout Mono, мс | Mono min–max, мс |
|---|---|---|---|---|
| Исходные семейства | 119 | 4 / 4 | 32,9 | 30,3–41,4 |
| Редкие поднаборы отдельно | 12 | 0 / 4 | 23,0 | 18,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 завершённых замера разделения семейств в CSV3. Повторный preload не означает повторное применение шрифта
В серии с разделением семейств после старта JS было пять шрифтовых ответов вместо трёх. Отдельное исследование 24 октября установило источник: ранние Latin и Cyrillic запрашиваются через HTTP-заголовок Link, а позже декодер React Flight обрабатывает две записи HL и React DOM добавляет соответствующие link rel=preload в DOM. Ранние HTTP-подсказки не создали DOM-элементов, которые мог бы найти клиентский реестр. Стек инициатора и наблюдение DOM подтвердили этот путь в зафиксированном опубликованном бандле. Но добавление подсказки само по себе ещё не доказывает новую передачу по сети.
| Режим | Поздние DOM-подсказки | Ответы шрифтов | Медиана LCP, мс | LCP min–max, мс |
|---|---|---|---|---|
| Кеш выключен, no-store | 2 | 5 | 2 644 | 2 524–2 892 |
| Кеш включён, no-store | 2 | 3 или 5 (по два запуска) | 3 044 | 2 604–3 892 |
| Кеш включён, immutable | 2 | 3 | 2 728 | 2 496–3 816 |
| Лаборатория без двух Flight HL | 0 | 3 | 2 706 | 2 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