Оценка девяносто восемь из ста в автоматическом аудите выглядит победой. Разработчик ставит ленивую загрузку на все картинки подряд, убирает из разметки предзагрузку, которую аудит назвал лишней, и метрики зеленеют. Через неделю служба поддержки приносит жалобу: главная страница на телефоне полторы секунды показывает пустоту вместо главного баннера, а переход по ссылке из письма открывается медленнее, чем вчера. Аудит измерял страницу, а не людей, и раскладка, сделанная ради оценки, оплатила баллы чужим удобством. Разобраться придётся в том, что ленивая загрузка и предзагрузка на самом деле дают проекту и что с них спрашивают настоящие посетители.

Ленивая загрузка как способ не платить за невидимое

Смысл ленивой загрузки в честной сделке с браузером: картинка, которую человек пока не видит, не скачивается вовсе. Нативный атрибут loading со значением lazy появился в Chromium в 2019 году, через год добрался до Firefox, а к 2022 году до Safari, так что сегодня это норма, а не экзотика. До нативной поддержки такие механизмы собирали на IntersectionObserver и обработчиках прокрутки, и каждая команда собирала их по-своему. Браузер подгружает картинку не в момент пересечения с экраном, а заранее, с запасом в одну-две тысячи пикселей, чтобы человек не замечал подгрузку на средней скорости.

Арифметика сделки прозрачна. Каталог с тридцатью изображениями по сто пятьдесят килобайт весит четыре с половиной мегабайта. Первый экран показывает четыре карточки, то есть шестьсот килобайт. Остальные почти четыре мегабайта лежат в разметке на случай, если человек докрутит список. Ленивая загрузка переносит эти мегабайты с момента открытия на момент прокрутки, и для тех, кто уходит после первых трёх экранов, они просто не тратятся. На мобильном тарифе это видимые деньги в кармане человека, в метриках проекта это освободившаяся полоса и быстрый первый экран. Ленивый атрибут работает и для встраиваемых окон с чужими плеерами, а они обычно тяжелее любой картинки на странице.

Есть и типовые места, где ленивая загрузка вредит незаметно. Картинки внутри всплывающих окон, открывающихся по клику, грузятся лениво только если окно живёт в прокрутке страницы; в отдельном контейнере с собственной прокруткой браузер запросто не поймёт намерения и повесит файл до конца времён. Каждый такой случай проверяется руками, а не слепой верой в универсальность атрибута.

Предел ленивости на самом дорогом элементе страницы

У каждого экрана есть изображение, ради которого человек и пришёл, и метрики зовут его главным контентным элементом. Помечать его ленивой загрузкой значит лично расписаться в пустом экране на первом визите: браузер узнаёт о существовании файла позже, чем мог бы, и главный образ появляется последним. Свежие выпуски Chromium ведут себя умнее разметки: со сто тридцатой версии движок сам распознаёт вероятных кандидатов в главный элемент и грузит их немедленно, даже если автор поставил ленивую пометку. Полагаться на такую эвристику всё же не стоит, она работает по общим соображениям, а не по знанию проекта.

Прямое решение, управлять приоритетом руками. Атрибут fetchpriority со значением high говорит браузеру, что файл важнее остальных, и поддержка дошла уже до всех трёх движков. Практическое правило проекта звучит жёстко: всё, что человек видит в первые полторы секунды, грузится жадно и с высоким приоритетом, всё остальное живёт на ленивой загрузке. В разметке это выглядит буднично:

<img src="/img/hero.jpg" alt="Новинки недели" width="1200" height="600" fetchpriority="high">
<img src="/img/tovar-17.jpg" alt="Кроссовки" width="400" height="400" loading="lazy">

Явные width и height во второй строке не случайность: зарезервированное место спасает страницу от прыжков вёрстки, когда картинки приезжают с опозданием. Прыжки портят впечатление сильнее медленной загрузки, потому что человек промахивается пальцем по ссылке, уехавшей из-под курсора.

Приоритет решает порядок, но не вес. Главная картинка весом в мегабайт останется медленной при любом приоритете, поэтому вслед за расстановкой приоритетов стоит пересмотреть сами файлы: отзывчивые версии через srcset, современный формат вместо старого JPEG, разумная ширина вместо отдачи четырёх тысяч пикселей для колонки в триста. Комбинация жадной загрузки правильной версии обычно сокращает главный элемент с мегабайта до пары сотен килобайт, и только после этого разговор о предзагрузке становится осмысленным.

Предзагрузка которая покупает скорость за чужой трафик

Предзагрузка делает противоположное: скачивает файлы раньше, чем они понадобятся. Инструментов три, и у каждого своя ставка. rel=preload указывает конкретный файл, критичный для текущей страницы. rel=prefetch тихо скачивает ресурс для вероятной следующей страницы. Правила спекулятивной загрузки в свежих Chromium позволяют даже заранее отрисовать следующую страницу целиком, когда браузер уверен в намерении человека. Все три технологии платят трафиком посетителя за шанс ускорить его путь.

Плата становится заметной там, где угадывание хромает. Скачанный вперёд скрипт, который так и не понадобился, это потраченные килобайты в никуда, и на объёмном сайте их набегает немало. Человек с безлимитом не заметит, у владельца пакета в пару гигабайт повод для раздражения появится. Браузеры помогают сдержанности: в режиме экономии трафика часть спекулятивных загрузок приглушается, но полагаться на эту любезность не стоит, контроль лучше держать в своих руках. Рабочие границы просты: предзагружать только критичное, угадывать следующую страницу по сильным сигналам вроде наведения курсора или уже видимой ссылки, и никогда не тянуть половину каталога впрок. Быстрый следующий переход приятен, а оплаченные впустую мегабайты помнятся дольше.

Проверить, во что обходится угадывание, можно прямо в журналах сервера. Запросы, приехавшие по предзагрузке и не сопровождённые ни одним следующим действием из той же сессии, честно показывают процент полезных угадываний. Пара часов разборки даёт эту цифру, и с ней разговор о расширении списка предзагрузок заканчивается сам собой, в любую сторону.

Бесконечная лента заслуживает отдельного предупреждения. Она дружит с ленивой загрузкой идеально, но плохо дружит с человеком: кнопка назад иногда выбрасывает в начало списка, ссылка сохранения позиции работает криво, а дойти до подвала страницы становится невозможным. Пагинация скучнее, зато честнее и к посетителю, и к предзагрузке, ведь следующую страницу пагинации легко угадать по ссылке, а следующую порцию ленты угадывает только сам механизм ленты.

Шрифты и критические стили где предзагрузка честная

Есть категория файлов, для которых предзагрузка честная по определению. Фирменный шрифт сайта весит десятки килобайт, нужен каждой странице и без подготовки вызывает мигание текста, когда буквы сначала рисуются запасным шрифтом, а потом перепрыгивают на настоящий. Одна строка в начале разметки снимает проблему целиком:

<link rel="preload" href="/fonts/montserrat-600.woff2" as="font" type="font/woff2" crossorigin>

Атрибут crossorigin для шрифтов обязателен: без него браузер скачает файл дважды, и предзагрузка превратится в двойную работу вместо экономии. Сюда же относятся критические стили первого экрана, собранные в основной документ, и главный скрипт, без которого страница не оживает. Такой список короткий по определению, если в него попадает больше трёх-четырёх файлов, стоит пересмотреть критерий отбора.

Критические стили, вырезанные в основной документ, живут своей жизнью: интерфейс меняется, вырезка остаётся, и через полгода она начинает описывать несуществующий экран. Вырезка, автоматическая на сборке, честнее ручной, потому что обновляется при каждой сборке без опоры на человеческую память.

Обратная сторона у любой предзагрузки одна. Файл скачан вперёд, но не использован, и браузер жалуется прямо в консоли:

The resource https://site.example/hero.webp was preloaded using link preload but not used within a few seconds.

Подобное предупреждение одновременно жалоба и подсказка: либо файл указан зря, либо в атрибуте as задан неверный тип, и браузер качает его второй раз с нуля. Проглядеть консоль после каждого изменения занимает минуту и держит список предзагрузок честным.

Метрики которые врут и цифры которые говорят правду

Автоматический аудит измеряет страницу в лабораторных условиях: эмулируется средний телефон, сеть режется до медленного соединения, процессор замедляется вчетверо, прогон один. Полезный диагноз, но условия искусственные и никогда не повторят живой трафик проекта с его географией, устройствами и привычками. Полевые метрики собираются у настоящих посетителей и оцениваются по процентильному правилу: главный контентный элемент должен появляться за две с половиной секунды у трёх из четырёх людей, отклик на ввод укладываться в пятую часть секунды, сдвиги вёрстки оставаться в пределах сотой доли.

Самое интересное начинается на стыке двух наборов цифр. Ленивая загрузка отлично выглядит в аудите, потому что аудит любит лёгкие страницы, но она способна ухудшить полевые метрики, если главный элемент попал под раздачу. Предзагрузка наоборот способна вытянуть полевые цифры, одновременно подсрезав оценку за лишний трафик. Отсюда рабочее правило: принимать решение по полевым цифрам до и после изменения, на своей аудитории и своём трафике. Собирать их несложно, браузерные счётчики отрисовки и задержек отправляются одним небольшим запросом в систему учёта, и к вечеру видна картина по странам и устройствам. Аудит после этого читается как пояснение к диагнозу, а не как приказ к действиям.

Со скриптами баланс выглядит иначе. Тяжёлые модули, редакторы, карты, библиотеки графиков, стоит резать динамическим импортом и грузить при первом реальном использовании, а не на старте. Ленивость скриптов даже честнее ленивости картинок: человек готов подождать мгновение при открытии диалога, но не готов платить стартовой задержкой всей страницы за код, который не откроет никогда. Осторожность нужна с файлами, влияющими на самый первый клик: лениво подключённый обработчик, приезжающий позже, чем человек успел нажать, выглядит как сломанная кнопка, и никакая экономия трафика этого не оправдает.

Каталог товаров где всё сходится в одну схему

Собрать описанные правила в рабочую схему проще всего на примере каталога:

  1. главный баннер и первая линия карточек грузятся жадно, с высоким приоритетом и явными размерами;
  2. остальные карточки живут на ленивой загрузке с зарезервированным местом под каждую картинку;
  3. шрифт заголовков предзагружается ровно один, тот, что рисует главный экран;
  4. следующая страница каталога предзагружается по сильному сигналу, когда курсор задержался на ссылке пагинации;
  5. плейсхолдеры показываются на месте ещё не приехавших картинок, чтобы вёрстка не прыгала;
  6. полевые метрики снимаются до и после изменения, и схема правится по цифрам реальных людей, а не по оценке аудита.

Схема работает и в обратную сторону: убрать из неё строку можно только с цифрами в руках. Уберёте предзагрузку шрифта, получите мигание на каждом переходе. Откажетесь от запасного места под картинки, получите прыжки вёрстки на каждом медленном соединении. Каждый пункт держит что-то конкретное, и в этом главная ценность: схему легко объяснить команде без споров о вкусах, а новый разработчик понимает её за один вечер.

Вывод который возвращает метрики на их место

Ленивая загрузка и предзагрузка не конкуренты, а пара инструментов с противоположными ставками. Ленивость экономит трафик на невидимом и не имеет права трогать главный экран. Предзагрузка покупает скорость будущего перехода и обязана быть скупой, потому что платит чужими мегабайтами. Погоня за оценкой аудита ведёт к тому, что оба инструмента применяются туда, где они красят отчёт, вместо мест, где они полезны людям. Честный баланс собирается из простых вещей: приоритеты расставлены руками, главный экран жадный, невидимое ленивое, каждая предзагрузка держится на полевых цифрах. Оценка в автоматических аудитах при такой раскладке растёт сама, как побочный эффект заботы о живых посетителях, и это единственный порядок действий, который не мстит через неделю после релиза.