Четыре сотни в PageSpeed, и что именно для этого понадобилось

Google PageSpeed Insights даёт этому сайту 100 баллов за производительность, доступность, лучшие практики и SEO - и на десктопе, и на мобильном. Вместо того чтобы выложить скриншот и на этом закончить, вот каждое изменение, которое к этому привело.

Ряд стеклянных пластин, уходящих вглубь, словно водопад загрузки

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

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

Итак: PageSpeed Insights, четыре сотни, десктоп и мобильный. Вот что за ними стоит.

Начните с того, чтобы не отгружать фреймворк

Этот сайт - статический HTML с одной таблицей стилей и одним скриптом. Нет клиентского роутера, нет шага гидратации, нет рантайма компонентов. Это решение стоит больше, чем все перечисленные ниже оптимизации вместе взятые, потому что самый быстрый JavaScript - тот, который так и не был отправлен.

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

Между HTML и первой отрисовкой не стоит ничего

Таблица стилей минифицируется на сборке и встраивается в каждую страницу элементом <style>. Отдельный CSS-файл означал бы второй круг обращений до того, как хоть что-то можно нарисовать, а таблица достаточно мала, чтобы байты стоили дешевле запроса. То есть первый же ответ содержит всё необходимое для отрисовки.

Montserrat размещён у нас как вариативный woff2, латиница и расширенная латиница, веса от 400 до 800 в одном файле. Никакого рукопожатия со сторонним шрифтовым сервисом, никакого DNS-запроса к чужому домену до того, как появится текст. Каждая страница предзагружает латинский поднабор.

Три вещи, которые раньше работали, пока читатель ждал

Отсюда и пришла большая часть измеримого выигрыша.

  • WebGL-шейдер. Стеклянный эффект за карточками шагов на главной компилирует фрагментный шейдер, и это блокировало главный поток почти на секунду. Секция, которую он украшает, находится далеко ниже первого экрана, поэтому IntersectionObserver теперь придерживает его, пока читатель не окажется в двух экранах от неё.
  • Google Tag Manager. Около 120 КБ стороннего JavaScript, от которого на странице ничего не зависит. dataLayer по-прежнему создаётся сразу, поэтому ни одно событие не теряется, но сам контейнер подгружается по первому взаимодействию или когда браузер после загрузки уходит в простой - что наступит раньше.
  • Миниатюры шагов. Примерно 590 КБ анимированных GIF, которые видны только на раскрытой карточке. Они хранят свой адрес в data-src, и скрипт подставляет src, когда карточка открывается. На устройствах с курсором весь ряд прогревается при наведении, так что раскрытие всё равно ощущается мгновенным.

Картинки: скучные 400 КБ

Крупные декоративные изображения отдаются в AVIF с оригиналом как запасным вариантом через <picture>, и только на главной это около 400 КБ.

Одна ловушка, которую стоит передать дальше. CSS-фон не получает такой же обработки: он остаётся обычным url() в формате, который читает любой браузер. image-set() вместе с type() корректно разбирается только начиная с Safari 17 - более старый Safari принимает объявление и затем не рисует ничего. Все фоновые изображения кейсов для таких посетителей молча исчезали, хотя в Chrome всё выглядело идеально. Наша сборка теперь отказывается работать, если в таблице стилей встречается image-set(. Выбор формата - дело <picture>, где все браузеры ведут себя правильно уже десять лет.

Две вещи, которые мы проверяем перед любым перекодированием:

  • В каком размере это реально отображается? Одна фотография товара была оригиналом 3072 × 4096, который показывали в блоке 624 × 680.
  • Градиенту не нужно разрешение. Затемняющий слой над каждым заголовком кейса весил 557 КБ при 2880px. При 1440px в AVIF он весит 8 КБ и отличается от оригинала в пределах трёх единиц, потому что background-size: cover растягивает его в любом случае.

У каждого <img> проставлены явные width и height, поэтому при загрузке ничего не прыгает. Это половина оценки, связанная со сдвигом макета, и она достаётся бесплатно. Плюс loading="lazy" и decoding="async".

Видео, на котором сайты обычно и проигрывают

За заголовком крутится зацикленная стеклянная анимация. Она же - самое тяжёлое на странице, поэтому к ней относятся как к необязательной.

Кадр-постер рисуется средствами CSS как фон секции, поэтому заголовок никогда не пустой, а видео никогда не входит в первую отрисовку. Каждый ролик закодирован четырьмя способами - 1080p и 720p, каждый в H.264 и HEVC, - и скрипт выбирает наименьший файл, который покрывает отображаемый размер, предварительно проверив, умеет ли браузер декодировать HEVC. Телефон никогда не скачивает декоративный фон в 1080p.

Оно полностью пропускается при prefers-reduced-motion или Save-Data, ждёт события load, а всё, что ниже первого экрана, ждёт, пока не окажется в пределах одного экрана от видимости. Откройте любую нашу страницу с середины - и видео заголовка не загрузится вовсе.

Кеширование, которое ничего не стоит настроить правильно

Годовой неизменяемый кеш для статики, no-cache для HTML и сжатие для всего текстового. В адресах ресурсов стоит версия, поэтому изменённый файл - это изменённый адрес, и длинный кеш никогда не оказывается обманом.

Остальные три сотни - это не производительность

Доступность, лучшие практики и SEO - это скорее дисциплина, чем инженерия, и поддерживать их куда дешевле, чем потом дорабатывать.

  • Один h1 на страницу, секции - h2, карточки - h3, и ни одна секция не выдумывает себе собственный размер заголовка, чтобы выглядеть важнее.
  • Ссылка пропуска, настоящие ориентиры, aria-labelledby у каждой секции, видимые состояния фокуса и контраст, проверенный против того фона, на котором элемент действительно находится, а не того, под который его рисовали.
  • Канонический адрес и hreflang на каждой странице, во всех трёх языках, и каждая страница объявляет себя среди собственных альтернатив.
  • Структурированные данные, собранные из того же источника, что и видимая страница, поэтому разметка и содержание не могут разойтись. Наша страница вопросов и её схема FAQPage собираются из одного массива; второй копии, которую можно забыть обновить, просто нет.

Чего оценка не говорит

Стоит сказать прямо: PageSpeed - это лабораторное измерение на смоделированном устройстве и соединении. Это хороший ориентир и действительно полезный чек-лист, но значение имеют полевые данные от настоящих посетителей в настоящих сетях. Идеальный лабораторный результат без полевых данных за спиной - это хороший скриншот, а не законченная работа.

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

И да: когда PageSpeed выдаёт четыре сотни, скриншот делают до того, как он передумает.