
Сайт успешно протестировали на мастере, выкатили на прод, провели контрольное тестирование — вроде всё хорошо. Он работает пару месяцев — и вдруг приходит задача от SEO «увеличить скорость загрузки» или «исправить просевшее количество баллов в PageSpeed». Причём ничего принципиально нового не добавляли, просто наполняли контентом.
Для примера рассмотрим типичную ситуацию и некогда оптимизированный проект с минимумом изображений на стеке Python/Django/SCSS — на нём будем делать правки.
Для начала смотрим загрузку «на глаз» — вроде нормально. Но отдел SEO упорно ставит задачу и требует исправить баллы в PageSpeed, потому что его метрики влияют на ранжирование в поиске.
Ок, открываем его — и видим оранжевую зону и непонятные аббревиатуры CLS, LCP и др.
А всё потому, что PageSpeed ориентирован на наиболее жёсткий вариант условий открытия сайта, и он проверяется без кэша в условиях мобильного интернета (скорость варьируется в рамках от скоростного 3G до медленного 4G).
Начинаем разбираться — и выясняется, что есть общая проблема, которая почти всегда повторяется. А именно — изображения. Их неправильно готовят: не ресайзят, не соблюдают соотношение сторон, не конвертируют, заливают «как есть». Зачастую на мобильной версии сайта вполне можно найти изображение во всю ширину десктопа, сжатое силами CSS. Выглядят одинаково, но вес — совершенно разный. И на вопрос «Почему так сделано?» — ответ, как правило, — «Всё взято с макета, соответствие проверено по perfect pixel» или «с отделом дизайна согласовано, стилистика не нарушается». Получается, что риски надо было закладывать на этапе дизайна и разработки, но по факту их исправляют, когда продакшн уже горит.
Поэтому дальше разберём пользу сжатий и конвертации на живом примере и покажем, как их править. А заодно — как делать, чтобы в процессе разработки они не повторялись.
Пункт 0. Первичная оценка масштабов катастрофы
Открываем DevTools, вкладку «Сеть». Видим запросы.
Как правило, наиболее критичные проблемы — логотип, изображение в хедере, иконки интерфейса и т. д. Всё потому, что они находятся на первом экране и влияют на то, как быстро пользователь увидит сайт и получит первое впечатление.
Открываем калькулятор и подсчитываем, какой же объем изображений у нас на сайте грузится в итоге: то, что раньше весило килобайты, теперь перевалило за мегабайты. А ведь это только изображения. Они — весомая, но не единственная часть нагрузки.
Итог: сайт не просто работает, а активно обрастает тяжелым контентом и внешними сервисами. С этим надо что-то делать.
Пункт 1. Смотрим размеры и формат
Дизайнеры нарезают красивые макеты, но в админке картинки заливает контент-менеджер, и его логика проста: «Если поле позволяет загрузить PNG в 4K — почему бы и нет? Предварительная конвертация требует времени, а время — деньги компании».
А PageSpeed упорно пишет, что размеры изображений выше, чем нужно, и съедают лишние ресурсы.

Часть изображений не соответствует размерам контейнера на мобильной версии, а также отсутствует сжатие AVIF — выдаёт рекомендации по загрузке изображений и LCP
Что же делать разработчику, чтобы решить проблему? Правильно — заложить обработку на уровне кода заранее. Или, если проект уже в проде, внедрить её сейчас.
Дальнейшие действия выглядят так (Можно выполнять вместе или выборочно. Логично, что изображение AVIF-формата уже оптимальнее, чем JPEG. А если нарезать мобильную версию картинки, она будет легче, чем десктопная).
-
Смотрим, какие размеры изображений используются на ключевых разрешениях и делаем автоматическую нарезку на эти размеры. Например, для фото в шапку, которое идёт на всю ширину страницы, могут быть валидны вариации десктоп (1280px), планшет (768px), мобилка (320px).
-
Конвертируем в WebP, AVIF или JPG. Часто получается так, что расширение PNG используется для всех изображений подряд, в том числе там, где нет прозрачности. При этом упускается, что даже оптимизированный PNG весит больше JPG. При автоматике наличие прозрачности отследить сложно, но при ручной заливке стоит поменять расширение PNG-исходника на правильное.
Когда с PNG разобрались, переходим к генерации веб-форматов. Почему именно WebP и AVIF? Потому что именно они проектировались для WEB, и обеспечивают наилучшее соотношение качество / вес / поддержка.
Тут следует учесть, что да — иногда WebP может весить больше исходника, а AVIF больше WebP — но это исключения. Для автоматического конвертирования как правило, это не критично. В случае со статикой — выбираем только оптимальные по весу форматы.
В идеале должно соблюдаться неравенство: Вес исходника > Вес WebP > Вес AVIF.
-
Сжимаем без видимых потерь качества. (Например, как вариант, с помощью Pillow). Для автоматического сжатия можно начать со сжатия на 20% — при таком значении видимые потери в качестве как правило почти незаметны. А дальше можно подогнать по необходимости.
При конвертации и сжатии стоит учесть:
-
То, что уже залито — зачастую легче перезалить руками. Тогда вы не потеряете контроль над тем, что получилось после нарезки.
AVIF — это достаточно агрессивное сжатие. При ручной заливке мы вполне можем сделать отображение результата в админке и контролировать качество. При автоматике — можем получить вместо изображения набор цветных полосок. С WebP, как правило, таких проблем нет, но контроль лишним не бывает.
-
Для CKEditor и подобных плагинов — парсим содержимое (BeautifulSoup), подменяем фото, сохраняем в отдельное поле для отображения.
-
Меняем тег <img> на <picture> с источниками для разных форматов и разрешений и вариаций. В этом случае браузер загрузит только ту вариацию, чей атрибут type соответствует его возможностям. Тут стоит учесть: согласно исследованиям, больший объем интернет-трафика используется смартфонами, и Google при индексации сайта использует мобильную версию сайта. Поэтому в теге img указываем в качестве fallback путь к мобильной версии изображения, а в атрибутах width и height — размеры мобильной версии.
Итог этого пункта: даже учитывая, что изображение изначально было в формате AVIF, после оптимизации мы снизили вес картинки, а соответственно, и вес передаваемых с сервера данных более чем на 50 килобайт.
На первый взгляд кажется, что это немного. Но вспомним, что страницы, как правило, с блоками. Например:
-
блок статей с их превью обычно содержит 3–4 статьи на видимом слайде;
-
блок отзывов с фото клиентов обычно содержит 2–3 отзыва на видимом слайде.
Возьмём минимальное количество статей и отзывов в блоках, нашу выгоду от сжатия одного изображения из примера — и сделаем примерный расчёт.
50 килобайт (из примера) * (3 статьи + 2 отзыва) = 250 килобайт. Звучит уже по-другому — сократили вес передаваемых данных на четверть мегабайта.
Пункт 2. Ленивая загрузка и предзагрузка
Всё, что видно на первом экране мобильной версии, грузим сразу (preload). Всё, что ниже, — с loading=»lazy» и decoding=»async». И обязательно проставляем ширину и высоту, чтобы не было скачков при загрузке.
Пример того, что должно получиться по итогу выполнения первых двух пунктов.
Head (например, если в бэкенде проверяется user-agent и отдаются булевы значения возможности использования AVIF/WEBP): {% if page.use_avif %} <link rel="preload" as="image" href="{{ page.background.mobile.url_avif }}" type="image/avif" media="(max-width: 768px)"> <link rel="preload" as="image" href="{{ page.background.tablet.url_avif }}" type="image/avif" media="(min-width: 769px) and (max-width: 1024px)"> <link rel="preload" as="image" href="{{ page.background.desktop.url_avif }}" type="image/avif" media="(min-width: 1025px)">{% elif page.use_webp %} <link rel="preload" as="image" href="{{ page.background.mobile.url_webp }}" type="image/webp" media="(max-width: 768px)"> <link rel="preload" as="image" href="{{ page.background.tablet.url_webp }}" type="image/webp" media="(min-width: 769px) and (max-width: 1024px)"> <link rel="preload" as="image" href="{{ page.background.desktop.url_webp }}" type="image/webp" media="(min-width: 1025px)">{% else %} <link rel="preload" as="image" href="{{ page.background.mobile.url }}" type="image/jpeg" media="(max-width: 768px)"> <link rel="preload" as="image" href="{{ page.background.tablet.url }}" type="image/jpeg" media="(min-width: 769px) and (max-width: 1024px)"> <link rel="preload" as="image" href="{{ page.background.desktop.url }}" type="image/jpeg" media="(min-width: 1025px)">{% endif %}Для фото, которые спрятаны за первым экраном:<picture> {% if property.preview.normal.srcset_avif %} <source srcset="{{ property.preview.normal.srcset_avif }}" sizes="100vw" type="image/avif" media="(min-width: 1024px)"> {% endif %} {% if property.preview.normal.srcset_webp %} <source srcset="{{ property.preview.normal.srcset_webp }}" sizes="100vw" type="image/webp" media="(min-width: 1024px)"> {% endif %} <source srcset="{{ property.preview.normal.srcset }}" sizes="100vw" type="image/jpeg" media="(min-width: 1024px)"> {% if property.preview.mobile.srcset_avif %} <source srcset="{{ property.preview.mobile.srcset_avif }}" sizes="100vw" type="image/avif"> {% endif %} {% if property.preview.mobile.srcset_webp %} <source srcset="{{ property.preview.mobile.srcset_webp }}" sizes="100vw" type="image/webp"> {% endif %} <source srcset="{{ property.preview.mobile.srcset }}" sizes="100vw" type="image/jpeg"> <img src="{{ property.preview.mobile.url }}" alt="{{ property.full_address() }}" loading="lazy" decoding="async" width="{{ property.preview.mobile.target_width }}" height="{{ property.preview.mobile.target_height }}"> </picture>
Итог этого пункта:
-
побороли дергания страницы, учитываемые показателем CLS через установку атрибутов width/height;
-
ускорили первый экран, самый критичный для первичного посещения пользователем (preload);
-
убрали из начальной загрузки то, что пользователь при первом открытии сайта потенциально не будет видеть (lazy + async).
Пункт 3. background-image
Картинка в стилях — не проблема. Скриптом определяем поддержку WebP/AVIF (например, через загрузку точки-картинки) — это те форматы, которые поддерживают основная часть браузеров. Далее добавляем класс, например, webp-allowed, к тегу <html>. В стилях через вложенные селекторы переключаем формат изображения в зависимости от формата и разрешения.
Пример того, что может получиться на языке SCSS:
banner { // Мобильная версия (базовая, до 768px) background-image: url('/static/img/banner-mobile.jpg'); .avif & { background-image: url('/static/img/banner-mobile.avif'); } .webp & { background-image: url('/static/img/banner-mobile.webp'); } // Планшет (768px и выше) @media (min-width: 768px) { background-image: url('/static/img/banner-tablet.jpg');.avif & { background-image: url('/static/img/banner-tablet.avif');}.webp & { background-image: url('/static/img/banner-tablet.webp');} } // Десктоп (1280px и выше) @media (min-width: 1280px) { background-image: url('/static/img/banner-desktop.jpg');.avif & { background-image: url('/static/img/banner-desktop.avif');}.webp & { background-image: url('/static/img/banner-desktop.webp');} }}
Итог этого пункта: для background-image нельзя использовать <picture>, поэтому мы подложили поддержку форматов через класс на <html>. Скрипт проверил, с чем работает браузер (AVIF, WebP или только JPEG/PNG), добавил класс, а SCSS уже сам подставил правильный URL. В итоге фон на любом устройстве грузится в оптимальном формате, без лишнего веса.
Пункт 4. SVG
SVG — это векторная графика, она не должна весить мегабайты. Увидели подозрительно большой вес SVG, открываем файл в блокноте (или любом другом редакторе текста). Если внутри data:image/jpeg;base64, значит, дизайнер просто воткнул растровую картинку. Идём к нему и договариваемся о нормальном векторе, содержащем лишь векторные примитивы.
Также лучше сразу собрать загружаемые по отдельности мелкие SVG в спрайт и подключать один файл, особенно если их много (соцсети, иконки разделов).
Пункт 5. Выдыхаем
Изображения сконвертированы, оптимизированы и нарезаны.
Это только начало, но в то же время — одна из самых весомых частей. Остальное (скрипты, шрифты, кэширование) — тема следующих статей.
Итак, качество сайта зачастую страдает в силу экономии ресурсов, и это видно далеко не сразу. Технологии не стоят на месте: постоянно вводятся новые доработки в расчеты баллов и совершенствуются методы сжатия. Если не решать проблемы сразу, в дальнейшем они превратятся из периодических мелких задач в одну крупную, которую будет тяжело править без вреда контенту и затрат времени. Задача разработчика, продумать риски заранее.
Напоследок — краткий чек-лист, что можно сделать в будущем, чтобы заложить оптимизацию заранее:
-
Договориться с дизайнером — отдавать иконки в SVG (настоящем, без base64), а растровые изображения — в максимальном разрешении не больше 1920px (или нужном для макета).
-
Настроить автоматическую обработку загрузок — ресайз, конвертацию в WebP/AVIF, например, в рамках Django можно использовать сжатие через Pillow прямо в момент заливки в админке.
-
Использовать <picture> и srcset с рождения — не заменять потом, а сразу верстать с адаптивными картинками.
-
Заложить width/height для всех img — даже если кажется, что «потом проставим».
-
Сделать спрайты для мелких SVG (иконки, соцсети) — одним файлом, а не десятками запросов, так как каждый запрос — это обращение к серверу за данными и трата его ресурсов. Чем меньше обращений, тем меньше на него нагрузка и больше ресурсов на других клиентов.
Автор статьи — Михаил Клинов.
НЛО прилетело и оставило здесь промокод для читателей нашего блога:-15% на заказ любого VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.
ссылка на оригинал статьи https://habr.com/ru/articles/1045832/