Привет! Меня зовут Андреева Саша, я веб-разработчик в компании iSpring. Два года назад мы столкнулись с тем, что всё больше и больше страниц нашего ведущего сайта начали падать в выдаче, а рост органического трафика заметно уменьшился. Основной причиной было то, что просели показатели сайта — мы постоянно обновляли страницы, но не уделяли должное внимание оптимизации. Мы проанализировали показатели, выбрали инструмент для отслеживания данных по всем страницам, в нём же настроили алерты и взялись за активную работу по оптимизации.
Перед вами рабочий чек-лист, в нём собраны основные наработки и советы по оптимизации, которые мы реализовали и продолжаем применять. Если вы работаете с CMS, то помимо перечисленных, есть дополнительные способы улучшить показатели — спрашивайте в комментариях :)
Инструменты для проверки качества оптимизации
Lighthouse выполняет тестирование на одном устройстве и с одной конфигурацией, что даёт точные, но не всегда отражающие реальное поведение пользователя данные.
Core Web Vitals — набор показателей, которые оценивают опыт пользователей на сайте с точки зрения производительности и удобства взаимодействия.
Для проверки показателей мы используем сервис DebugBear, где собрали список нужных страниц, сравниваем их по показателям, периодам и типам устройств. Также показатели можно проверять в PageSpeed Insights и WebPageTest.

При работе с сайтом основное внимание мы уделяли улучшению LCP(Largest Contentful Paint), чуть меньше — FCP (First Contentful Paint) и TBT (Total Blocking Time). Но естественно, оптимизация положительно сказалась и на остальных показателях, чему мы весьма рады :)
Оптимизация страницы
1. Картинки
Оптимизация изображений на сайте напрямую влияет на Largest Contentful Paint (LCP): сжатие изображений, предзагрузка картинок первого экрана и использование правильных форматов улучшают показатель.
1.1. Форматы
Для лого и иконок используем формат svg, который позволяет масштабировать изображения без потери качества. Для остальных картинок используем формат webp, который сохраняет качество и при этом сильно уменьшает вес картинки.
Наши размеры для разных экранов:
'img_min_src' => до 430px
'img_min_src_x2' => до 430px * 2 для ретины (дисплеев на устройствах Apple)
'img_medium_src' => до 1920px
''img_medium_src'_x2' => до 1920px * 2 для ретины
'img_large_src' => для экранов больше 1920
'img_large_src_x2' => для экранов больше 1920 * 2 для ретины
Обязательно сжимаем картинки, например, с помощью сервиса compresspng или iloveimg. В результате сжатия получаем экономию в 50-70% от веса изображений. В статье про оптимизацию графики для Retina-экранов объясняется, зачем для ретины увеличивать картинки в два раза.
1.2. Автоматическая конвертация изображений.
Мы написали скрипт, который перевел все существующие картинки в формат webp, и конвертирует новые загружаемые png и jpg в webp. Используем библиотку modernizr для проверки на поддержку webp, а для конвертации библиотеку cwebp.
Если интересно узнать подробнее про библиотеку, могу рассказать в комментариях, спрашивайте :)
1.3. Тег picture
Применяем тег <picture> для использования картинок в зависимости от размера экрана пользователя. Picture позволяет указать разные изображения для различных размеров экрана или плотности пикселей, чтобы браузер мог выбрать оптимальную версию изображения для каждого пользователя.
Пример использования тега:
<picture class="picture_class">
<source srcset="img_min.png" media="(max-width: 430px)">
<source srcset="img_medium.png" media="(max-width: 1920px)">
<source srcset="img_large.png" media="(min-width: 1921px)">
<img class="img_class"
alt="image description"
title="image title"
width="680"
height="530"
loading="lazy"/>
</picture>
1.4. Лайфхак css
Смотрим на дизайн и, если можно, разбиваем его на элементы. Например, картинку, представленную ниже, лучше разделить на два элемента: сама картинка и зелёный фон. Картинку выгружаем как обычно, а фон рисуем отдельно — псевдоэлементом.

2. Шрифты
Оптимизация шрифтов на сайте влияет на показатели FCP, LCP и CLS(Cumulative Layout Shift):
блокировка рендеринга при загрузке веб-шрифтов может задерживать отображение контента,
оптимизация загрузки шрифтов напрямую влияет на скорость первичного отображения страницы,
отрисовка шрифтов влияет на стабильность макета.
2.1. Оставляем только нужное
Чтобы ускорить загрузку шрифтов, оставляем только используемые языки, необходимые начертания и символы. Такая чистка помогает уменьшить размер шрифта. Убрать лишнее поможет сервис для оптимизации quick-site-upgrade.
2.2. Отрисовка шрифтов
В первые секунды, пока браузер подключает шрифты к странице, пользователь видит тот альтернативный шрифт, который вы указали. В нашем случае это 'Roboto' и семейство sans-serif:
$actualFontFamilyRegular: 'Euclid Circular B Regular', 'Roboto', sans-serif;
$actualFontFamilyMedium: 'Euclid Circular B', 'Roboto', sans-serif;
$actualFontFamilySemiBold: 'Euclid Circular B SemiBold', 'Roboto', sans-serif;
$actualFontFamilyBold: 'Euclid Circular B Bold', 'Roboto', sans-serif;
Чтобы увидеть, как шрифт меняется, поставим скорость загрузки страницы на Slow 3G:

Если ваш альтернативный шрифт сильно отличается от кастомного, размер блока будет лишний раз “скакать” и перерисовываться. Поэтому важно максимально близко подобрать размер и начертание шрифта, который отображается до загрузки вашего кастомного, чтобы перерисовка была минимально-заметной.
3. Стили
3.1. Разделение стилей страницы на mobile & desktop
Для стилей всех блоков используем подход Mobile First, а для первого блока выносим в отдельные файлы стили для мобилки и десктопа и подключаем их в зависимости от используемого устройства.
3.2. Critical css
Браузер загружает css до отображения страницы, а это значит, что css блокирует рендер и от него напрямую зависит показатель FCP.


Критичными (critical css) для страницы являются те стили, которые необходимы для отображения первой видимой части страницы. Их стоит вынести инлайново в <head> HTML-документа, что позволит максимально быстро отрисовать содержимое первого экрана для пользователя, а остальные стили можно подгружать позже. Важно сделать это автоматически, чтобы стили сами добавлялись инлайново при сборке или загрузке страницы.

В critical css у нас чаще всего вынесены шрифты, так как именно они занимают бóльшую часть экрана. Если файл шрифтов у вас подключается в css, то обязательно добавьте его в critical css.
4. Отложенная загрузка
Все что можно загрузить отложено, — загружаем отложено. Главное — чтобы это не мешало первоначальной отрисовке страницы. Lazy loading значительно ускоряет начальную загрузку страницы, снимает лишние блокировки основного потока, чем улучшает не только LCP, но и FCP и TBT.
4.1. Картинки
Для картинок, добавленных через тег <img> используем атрибут loading="lazy". Для остальных картинок и иконок, которые добавляем через background или псевдоэлемент, используем IntersectionObserver и подгружаем картинки при попадании контента в зону видимости пользователя.
Для картинки прописали класс js-lazy-background по которому подключается скрипт lazyLoadBackground:
document.addEventListener('DOMContentLoaded', () => lazyLoadBackground())
const lazyLoadBackground = () => {
const lazyBackgroundClass = 'js-lazy-background'
const options = {
rootMargin: '100px 0px',
threshold: 0
}
const elements = document.querySelectorAll(`.${lazyBackgroundClass}`)
if ('IntersectionObserver' in window) {
let observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.remove(lazyBackgroundClass)
entry.target.classList.add('active')
observer.unobserve(entry.target)
}
})
}, options)
elements.forEach(element => observer.observe(element))
}
else {
elements.forEach(e => {
e.classList.remove(lazyBackgroundClass)
e.classList.add('active')
})
}
}
Важно не прописывать lazyload для картинки в первом экране, так как это замедлит отрисовку. Для тега img в первом экране прописываем fetchpriority="high" для предзагрузки важных изображений.
4.2. Скрипты
Подключаем скрипты по событию DOMContentLoaded с учётом IntersectionObserver. У тяжёлых элементов подключаем скрипты и стили только когда пользователь доскроллил до элемента.

Стили и скрипты библиотеки слайдера подключаются через скрипт lazyLoadSources:
const lazyLoadSources = params => {
const {
selector,
styles = [],
scripts = [],
callback,
} = params
const elements = document.querySelectorAll(selector)
if ('IntersectionObserver' in window) {
...
По нужному селектору подключаем библиотечные скрипты и стили для слайдера:
document.addEventListener('DOMContentLoaded', () => {
lazyLoadSources({
selector: '.js-slider-industries-container',
scripts: [
'/js/lib/tiny-slider/tiny-slider.min.js',
],
styles: [
'/css/lib/tiny-slider/tiny-slider.css',
],
callback: block => {
const controls = '#' + block.nextElementSibling.getAttribute('id')
...
Совет: чтобы можно было отложить загрузку JS, проверьте, как будет выглядеть страница без него прямо в браузере. Если требуется, поправьте стили, чтобы она выглядела аккуратно и без JS:
в консоли разработчика откройте командное меню через Ctrl+Shift+P или Cmd+Shift+P,
введите Disable JavaScript, выберите данную опцию и нажмите Enter.

5. Кэш
Использование кэша снижает нагрузку на основное хранилище, позволяет сократить время доступа к данным, даёт системе возможность выполнять больше действий одновременно и экономит трафик. Кэширование ресурсов значительно улучшает LCP и FCP, сокращая время до первого рендеринга.
5.1. Кэш на стороне сервера
Используя кэширование, мы генерим страницу один раз и сохраняем результат в памяти на определенный период времени — TTL (Time to live). Пока не истечёт срок действия TTL, клиент будет извлекать сохраненную в памяти версию страницы. Так как релизы проходят не каждый день и контент меняется далеко не на всех страницах, мы установили время кэширования на 7 дней.
Что не стоит кэшировать:
куки,
хендлеры,
корзину и аккаунт,
методы post и put,
страницы с ценами,
данные для залогиненных пользователей.
Если есть возможность, добавляйте кэширование в Memcached или Redis, а не в файлах, так как это увеличивает скорость получения данных. А подробнее про про настройку кэша в статье про Nginx cache.
5.2. Прогрев кэша
После чистки кэша первая отдача страницы пользователю может быть очень долгой, так как страница формируется с нуля. Чтобы долгая прогрузка не портила впечатление пользователя о сайте, мы написали скрипт для автоматического прогрева кэша на мобилки и десктоп.
6. Использование CDN
Мы работаем по всему миру, и если вы тоже, то это мастхэв: CDN (Content Delivery Network) — сеть серверов, расположенных в разных частях мира. Она доставляет контент пользователю (изображения, видео, стили, скрипты) с ближайшего к нему сервера. CDN улучшает скорость загрузки и производительность сайта, а также снижает нагрузку на основной сервер.
Подключаем CDN не только для статики, но для всей станицы, иначе пользы будет мало. Мы используем и рекомендуем платформу Cloudflare: она объединяет в себе CDN и инструменты для оптимизации веб-сайтов.
7. Ускорение кода
7.1. Чистка и упрощение кода
Мы полностью отказались от использования JQuery и привели JS проекта к единообразию, попутно удалив все неиспользуемые скрипты и библиотеки. Скрипты на чистом JS работают быстрее без промежуточного слоя, лучше поддерживаются браузерами и используют меньше зависимостей.
7.2. Замена JS на CSS
Всё, что можно сделать с помощью css, делаем без использования JS. Например, слайдеры можно реализовать с помощью свойства transform, а аккордеон с помощью transition.
7.3. Заглушки вместо блока
Для тяжелых блоков, которые долго прогружаются, ставим заглушку-скелетон. Заглушка даёт пользователю понять, что идет процесс загрузки и он вот-вот получит нужную информацию. Такой подход создаёт более профессиональное впечатление от сайта, и пользователи реже покидают страницу во время загрузки.

Итоги
Показатели, с которых мы начали усиленную оптимизацию, выглядят весьма грустно: 869 страниц требующих улучшений на мобилке и больше сотни на десктопе.
Несколько десятков закрытых задач по оптимизация — и ВЖУХ — мы улучшили показатели для мобилки и десктопа в 8 раз и полностью избавились от страниц с низкой производительностью. Ура!

Mpa3b_ru21
молодцы!