У нас сайт на SvelteKit с отрисовкой на сервере: тридцать с лишним страниц, немного скриптов, ничего тяжёлого. При этом Lighthouse показывал 74 балла, первая отрисовка – 4,2 секунды, отрисовка основного содержимого – 4,6. Для страницы, где нет ни одной картинки в контенте, это много.
Виноват был один тег.
Что показал разбор
Цепочка критических ресурсов выглядела так:
zvukopis.ru/ 256 мс 13,3 КБ
|- fonts.googleapis.com/css2?family=Unbounded… 668 мс 1,5 КБ <- блокирует отрисовку
|- fonts.gstatic.com/…/unbounded…woff2 1092 мс 50,9 КБ
|- fonts.gstatic.com/…/golostext…woff2 1091 мс 22,1 КБ
|- … всего 8 файлов шрифтов
Аудит render-blocking-resources называл единственный блокирующий ресурс: таблицу стилей Google Fonts. Разбор LCP по фазам: TTFB 610 мс, а задержка отрисовки – 3 993 мс, то есть 87% времени. Грузить было нечего: LCP – это текст, он просто ждал шрифты.
Механика простая и всем известная, но напомню. Браузер получает HTML, видит <link rel="stylesheet"> на чужой домен, останавливает отрисовку, идёт на fonts.googleapis.com: DNS, TCP, TLS – новое соединение к незнакомому хосту. Получает CSS, а в нём ссылки на второй чужой домен, fonts.gstatic.com. Ещё одно соединение. И только потом – сами файлы.
preconnect к обоим доменам у нас стоял, display=swap тоже. Это не помогло: preconnect ускоряет рукопожатие, но не отменяет того, что стилевой файл блокирует отрисовку.
Что сделали
Забрали шрифты к себе. Порядок такой.
1. Взяли те же файлы. Запросили у Google CSS с современным User-Agent и вытащили из него ссылки на woff2 и диапазоны символов. Важная деталь: у переменных шрифтов Google отдаёт один файл на семейство и набор символов, а не по файлу на начертание, – в CSS все веса ссылаются на один и тот же адрес.
2. Оставили нужные наборы символов. Кириллица и латиница обязательны: латиница подтягивается даже на полностью русской странице, потому что в ней живут пробел, цифры и пунктуация. Расширенную латиницу оставили ради одного знака – рубля (U+20BD). Вьетнамский и кириллицу-расширенную выбросили.
3. Положили файлы в исходники, а не в статику. Это ключевой момент для кеша. Файл из static/ отдаётся как есть, с обычными заголовками. Файл, на который есть ссылка из CSS внутри src/проходит через сборщик: он получает хеш в имени и попадает в _app/immutable/а туда сервер уже отдаёт cache-control: public, max-age=31536000, immutableГод кеша без единой строки конфигурации.
4. Объявили предзагрузку. SvelteKit по умолчанию предзагружает только скрипты и стили. Шрифты добавляются хуком:
export function handle({ event, resolve }) {
return resolve(event, {
preload: ({ type, path }) =>
type === 'js' || type === 'css' ||
(type === 'font' && /\/(unbounded|golos-text)-cyrillic\.[\w-]+\.woff2$/.test(path))
});
}
Предзагружаем только кириллицу двух шрифтов первого экрана. Остальные семь файлов браузер возьмёт сам, когда разберёт стили: лишняя предзагрузка отнимает полосу у того, что нужно сразу.
Тут есть тонкость, которая ловится только в отладчике. При отрисовке на сервере SvelteKit не вставляет <link rel="preload"> в разметку – он отправляет заголовок LinkВ <head> вы ничего не увидите, а в заголовках ответа будет:
link: <./_app/immutable/assets/unbounded-cyrillic.D0U_xfn4.woff2>;
rel="preload"; as="font"; type="font/woff2"; crossorigin; nopush
Теги в разметку он добавляет только при предварительной отрисовке. Мы сначала решили, что предзагрузка не работает, и полезли искать ошибку в хуке.
Замеры
Lighthouse 12.8.2, мобильный профиль, тот же URL, два прогона.
| Было | Стало | |
| Оценка производительности | 74 | 84 |
| Первая отрисовка | 4,2 с | 2,3 с |
| Отрисовка основного содержимого | 4,6 с | 3,9 с |
| Блокирующие ресурсы | таблица стилей Google | нет |
Первый прогон после изменений дал 48 баллов и 3,4 секунды заблокированного потока – явный выброс на холодном старте. Если меряете у себя, делайте минимум два прогона и берите не первый: разброс лабораторных замеров на одной и той же странице легко достигает двадцати баллов.
Шрифты теперь начинают грузиться на 150-й миллисекунде вместо 684-й – предзагрузка объявлена в заголовке ответа, то есть до разбора HTML.
Чего мы не получили
Вес не уменьшился. Файлы те же самые, те же 213 КБ на восемь наборов символов. Выиграли мы не байты, а соединения и блокировку отрисовки. Если нужен ещё и вес – надо резать глифы, например, оставить в заголовочном шрифте только те символы, которые в заголовках реально встречаются.
LCP всё ещё выше нормы. 3,9 секунды при норме 2,5. Причина уже другая: на первом экране анимации, а элементом LCP браузер выбрал логотип в шапке. Это отдельная история и, честно говоря, отдельная статья.
Лицензии. Шрифты с Google Fonts распространяются по SIL OFL. Класть woff2 к себе можно, но тексты лицензий положите рядом с файлами – это требование, а не вежливость.
Стоит ли оно того
Если у вас на сайте стоит <link> на fonts.googleapis.com – да, почти наверняка. Работы на час: скачать файлы, прописать @font-face с теми же unicode-rangeубрать preconnectобъявить предзагрузку двух начертаний первого экрана.
Плюс к скорости добавляется независимость: у части провайдеров fonts.gstatic.com отвечает нестабильно, и тогда первый экран вашего сайта ждёт чужого таймаута.
Автор делает Звукопись – расшифровку аудио и видео в текст. Сайт, о котором речь, – её витрина.
Изображение на обложке: Flaticon
Комментарии