Зачем это делается
Может показаться, что новостной сайт — это самая простая вещь для веб-разработчика: берем ID статьи, выводим контент из базы, и на этом всё. На самом деле к новостному сайту предъявляются крайне жесткие требования, которые связаны с тем, что такой сайт сильно конкурирует с другими новостными ресурсами за поисковый трафик. В том числе его производительность должна быть на высоком уровне.
Это связано с двумя целями:
- Ускорить загрузку для пользователей с медленным интернет-соединением и таким образом сделать использование сайта комфортнее для них.
- Повысить позицию сайта в поисковых системах, поскольку они измеряют эффективность работы веб-сайта и учитывают ее при ранжировании.
Как измерить производительность
Для измерения производительности используется такой инструмент, как PageSpeed Insights. Но он позволяет измерять только производительность приложений, которые уже выложены на хостинг. Для измерения локально используется инструмент Lighthouse, который встроен в инструменты разработчика Chrome.
С локальными оценками нужно быть осторожным. Lighthouse эмулирует медленное интернет-соединение для оценки производительности, но результат всё равно может существенно отличаться от того, что получится после выкладки на хостинг. Локальное измерение подойдет, если вы хотите примерно оценить, как изменения в коде повлияют на скорость работы сайта, но не даст никакого понимания того, какая производительность будет у «боевого» приложения.
Lighthouse измеряет такие показатели:
- первая отрисовка контента (FCP) — время от перехода пользователя на страницу до того, как пользователь увидит какую-либо часть контента (хотя бы индикатор загрузки);
- скорость загрузки основного контента (LCP) — время от перехода пользователя на страницу до того, как пользователь увидит основное содержимое страницы;
- общее время блокировки (TBT) — время, в течение которого пользователь не может взаимодействовать со страницей;
- Cumulative Layout Shift (CLS) — показатель перестройки страницы перед загрузкой.
Lighthouse сразу дает много полезных рекомендаций, выполнение которых позволяет улучшить эти показатели. Далее я расскажу о некоторых наиболее сложных в реализации моментах.
Общая архитектура приложения
Основную техническую сложность представляла реализация Server-Side Rendering. Поскольку большинство современных веб-сайтов построены на технологии Single Page Application, все элементы пользовательского интерфейса создаются «на лету», что сильно снижает производительность.
Выходом из этой ситуации стало применение технологии серверного рендеринга (SSR). HTML-элементы для основного информационного содержимого страницы создаются на сервере, затем клиентский JS-код только «гидрирует» уже созданные элементы, то есть добавляет им интерактивность.
Можно использовать готовые фреймворки Next.js (на основе React) и Nuxt.js (на основе Vue) либо серверные версии шаблонизаторов для «ручной» реализации SSR.
Использование серверного рендеринга помогает существенно снизить значение FCP.
Избегаем перестройки страницы
Другим важным моментом была необходимость избегать перестройки (reflow) страницы. Это ситуация, когда появление какого-либо элемента приводит к изменению размеров всей страницы. Например, такое происходит, когда на странице есть изображение. Как правило, оно подгружается чуть позже, чем остальной контент страницы, поскольку изображения имеют значительно больший объем, чем текстовый контент.
В итоге получается, что изначально у картинки была нулевая высота, но после того, как файл прогрузился, элемент изображения приобрел высоту, соответствующую файлу. Весь контент, идущий после этого изображения, сдвигается вниз. Это может создать большое неудобство для пользователя, который уже начал читать текст под иллюстрацией, а тут он внезапно сдвигается вниз — пользователь теряет позицию в тексте, которую он читал.
Выходом стало резервирование необходимого места на странице для изображения заранее. Для этого я поместил элемент изображения в контейнер, который будет иметь точно такой же размер, как и изображение, которое планируется загрузить. Чтобы сделать это корректно, важно заранее знать размер изображения, которое будет загружено. Для этого необходимо, чтобы в базе данных, где хранится выводимый контент, были записаны размеры всех используемых изображений. Мы написали серверный скрипт, который обходит все используемые изображения и сохраняет их размеры в базу.
В итоге в нашем случае CLS удалось снизить до нуля.
Используем «ленивую» загрузку изображений
Часто страница содержит сразу несколько изображений в разных частях статьи. При этом не факт, что пользователь вообще прокрутит контент до тех изображений, которые размещены внизу страницы.
Для того чтобы не замедлять загрузку страницы изображениями, которые пока не нужны, используется «ленивая» загрузка — изображения начинают загружаться только в тот момент, когда пользователь прокрутил страницу до места, близкого к тому, где начинается изображение. Первоначально загружается только то, что находится на первом экране. Ранее для реализации этого приходилось использовать сторонние JS-библиотеки. В современных же браузерах есть для этого нативное решение: установка у элемента img атрибуту loading значения lazy.
Масштабируем изображения на стороне сервера
Авторы статей стараются вставлять изображения высокого качества. Но в этом не всегда есть необходимость, ведь читатель ограничен разрешением экрана своего устройства. Для этого есть смысл масштабировать иллюстрации на стороне сервера.
Для решения этой проблемы у элемента img есть атрибут srcset. Для его использования необходимо сделать, чтобы на сервере хранились изображения в разном разрешении, либо масштабировать в реальном времени. Далее в зависимости от ширины экрана устройства пользователя загружаются разные варианты картинок.
Автоматизация измерений
Часто производительность даже хорошо оптимизированного приложения внезапно падает при подключении какого-либо стороннего скрипта на стороне front-end. Причем такой скрипт может выглядеть «невинно», но по факту тянуть за собой кучу зависимостей, которые резко увеличат время загрузки. Если у вас на проекте используется Continuous Integration, то можно подключить Lighthouse CI и оценивать влияние на производительность каждого коммита.
Результаты
После оптимизации веб-приложения улучшились параметры его производительности, что позволило новостному сервису выше подняться в результатах поиска.
-
В итоге могу посоветовать командам, которые хотят оптимизировать приложение:
- постараться с самого начала использовать подходящие инструменты — Next или Nuxt;
- добавить замер производительности в CI;
- использовать минималистичный код на JS, в крайнем случае можно переписать всё на jquery-style-код;
- заранее позаботиться о том, чтобы в базе были данные о размерах изображений;
- следовать рекомендациям, выводимым в Lighthouse.