Мы не раз читали на Хабре, почему важно иметь быстрые сайты, как это влияет на посещаемость, время на странице, глубину и прочее. Но каждый раз это инструкции о том, как поменять инфраструктуру серверов, потратить десятки часов на разработку и ощутимое количество денег. В случае больших корпораций, конечно же, такие действия оправданы. А маленькие стартапы обычно таким и вовсе не занимаются, фокусируясь на других задачах.
Мы же оказались где-то посередине. У нас были плохие показатели, но времени на какие-то значительные изменения не было. И несмотря на наличие ресурсов мы решили пойти самым простым путём и, как и гласит закон Парето, получить 80% результата за 20% усилий. Меня зовут Савичев Игорь, я работаю в 10D Самолета и мы занимаемся цифровизацией строительства на российском рынке. Мы развиваем IT-технологии в разных направлениях от девелопмента до финтеха. И сегодня я расскажу, можно ли сделать себе хорошо, быстро и не очень дорого.
Стройка – очень сложный и комплексный процесс, в котором участвуют тысячи людей, как на площадке, так и в офисе. А помимо человеческого фактора, на результат влияют погода, сроки поставок, качество планирования и координация данных. Мы в Самолет 10D поставили цель сделать работу каждого человека на стройке проще. Ведь каждый день мы строим не просто квартиры, а помогаем людям исполнять мечты о собственном доме. Если говорить о моей команде, то мы занимаемся системой контроля сроков всего девелоперского цикла.
Выявление проблем
Как и многие на рынке, для измерений производительности фронтенда мы используем Lighthouse – это автоматизированный инструмент Google с открытым исходным кодом для измерения качества веб-страниц. И после запуска проекта у нас всё было не очень гладко. По сравнению со “средней температуре по больнице”, то есть, по похожим сайтам в сети, наши значения были сильно ниже. Попадание сайта в красную зону означает, что опыт клиентов, которые заходят на сайт, будет негативным. Всё тормозит, плохо загружается, а следовательно, люди постараются заходить реже или вообще пользоваться чем-то другим. Например, метрика LCP (Largest Contentful Paint) одна из самых важных и она измеряет насколько быстро человек увидит основное содержимое сайта. И все сайты сейчас стремятся к уменьшению этого параметра.
С этим надо было что-то делать, но время играло не на нашей стороне, т.к. команды загружены разработкой новых функций и на оптимизацию, увы, сил не оставалось. А в процессе исследований выяснилось, что мы можем сделать всё практически бесплатно, реализовав несколько базовых принципов.
И тут же поделимся с вами списком, который наша команда сформулировала для анализа этого и будущих проектов в контексте сборщика Webpack:
Есть ли минификация JS/CSS:
Оптимизированы ли шрифты:
Есть ли оптимизация изображений:
Есть ли неиспользуемый код:
работает ли tree-shaking, т.е. используется правильный импорт библиотек;
Есть ли возможность подключить CDN
Есть ли алгоритмы сжатия в nginx
Возможно ли использовать «ленивую» загрузку блоков
Оптимизированы ли тэги
link
иscript
Можно ли поднять целевую сборку ECMA стандарта
Есть ли возможность отказаться от избыточных библиотек в бандле:
lodash/moment и прочие;
Минифицируем код и оптимизируем шрифты
Минификация JS и CSS является критически важной практикой в веб-разработке по нескольким весомым причинам. Во-первых, она способствует увеличению скорости загрузки веб-страницы. Минификация уменьшает размер файлов JS и CSS путем удаления ненужных пробелов, комментариев и сокращения идентификаторов, что позволяет клиентам загружать их быстрее. Это существенно улучшает пользовательский опыт и может снизить отказы от сайта из-за долгой загрузки. Кроме того, минификация улучшает безопасность кода. Путем удаления ненужных комментариев и сокращения имен переменных и функций, минификация делает исходный код менее читаемым для злоумышленников.
В итоге, минификация JS и CSS помогает ускорить загрузку веб-страниц, уменьшить сетевой трафик, сделать код менее уязвимым к атакам и повысить общую производительность вашего веб-сайта.
В дополнение к этому мы заменили все шрифты на формат woff2. Во-первых, в woff2 по сравнению с woff и sfnt изменились алгоритмы сжатия и возможность добавления мета-информации. Во-вторых, этот формат совместим с большинством современных веб-браузеров (с момента как похоронили IE 11). И наконец, WOFF2 позволяет уменьшить размер файлов шрифтов, ускоряя загрузку страницы и снижая нагрузку на сервер, что положительно сказывается на производительности и пользовательском опыте.
И разумеется, включили свойство font-display: swap;
- его основная суть — обеспечить быстрое отображение текста на странице, даже если запрошенные шрифты еще не загрузились полностью. Когда вы используете это свойство, браузер начинает рендерить текст с системными шрифтами, а затем, когда запрошенные вами шрифты загрузятся, они заменяют системные. Это предотвращает мигание или пустые места в тексте, которые могли бы появиться, если браузер ждал загрузки всех шрифтов перед началом рендеринга. Да, swap убирает Flash Of Invisible Text (FOIT), но добавляет Flash Of Unstyled Text (FOUT). Поэтому, когда кастомный шрифт подгружается, сайт всё равно "мигает" (происходит reflow + repaint). Но если оптимизируется метрика LCP, то FOUT предпочтительнее.
Оптимизация изображений
Если говорить об оптимизации изображений, то для сжатия интерфейсных SVG и PNG элементов мы использовали ImageMinimizerWebpackPlugin на этапе сборки. А вот для картинок в контенте существуют прекрасные сервисы, такие как https://tinypng.com/ и https://svgoptimizer.com/. TinyPNG, например, работает работает с png, webp и jpeg форматом. Само сжатие происходит путем изменения количества бит. То есть идет преобразование 24 битного изображения в 8 битное (уменьшается количество цветов). Вследствие этого существенно изменяется и сам размер файла.
Сжать можно более 70% размера файла. Изменение качества изображения почти не видимы для нас. Однако это не для всех фотографий. Если само изображение небольшое или содержит небольшое количество цветовых элементов, то при сжатии, ухудшения качества картинки мы не заметим. А вот если взять изображение с большим разрешением, да еще и с многообразным цветовым оформлением (например, какой-то красивый пейзаж), то качество здесь все же немного ухудшится и различия станут видимыми. Но нам это было не особо принципиально.
Поэтому, если вы хотите сжимать изображения без потери качества, то данный сервис стоит использовать для картинок небольшого разрешения или для скриншотов с малым количеством цветовых элементов. В принципе для многих сайтов такой сервис подойдет. Если и будет какое-то ухудшение в качестве, то это не так страшно. Главное, чтобы картинка была небольшого размера и более понятной для пользователя.
Чуть углубляемся: TreeShaking, CDN и Lazy Load
Tree Shaking - это важная оптимизация в современной разработке JavaScript. С этим методом, внедренным благодаря нововведениям import/export в стандарте ES6, можно избегать включения в итоговый бандл кода, который фактически не используется в приложении. Для активации Tree Shaking, необходимо установить режим "production"
при сборке проекта, что позволит системе определить и удалить неиспользуемые части кода, сократив размер итогового файла. Однако, для успешной работы Tree Shaking, необходимо использовать синтаксис import/export, чтобы система могла точно определить зависимости и удалить неиспользуемый код.
Использование Content Delivery Network (CDN) для обработки статических ресурсов в вашем проекте может значительно повысить производительность и снизить нагрузку на сервер. Для этого следует следовать нижеперечисленной краткой инструкции:
Заказываем бакет у команды DevOps: Первым шагом является запрос на создание S3-бакета, в котором будут храниться статические ресурсы, и привязанный к нему СВТ-сервис. Обратитесь к вашей команде DevOps с запросом на создание бакета, указав требуемые параметры и настройки.
Изменяем publicPath для статики: В вашем проекте необходимо настроить publicPath так, чтобы он указывал на URL CDN бакета. Это обеспечит доставку статических ресурсов через CDN вместо вашего сервера.
Настройка CI job для загрузки статики перед деплоем: Создайте и настройте задачу (job), которая будет отвечать за автоматическую загрузку статических ресурсов в бакет CDN перед каждым деплоем окружения. Это позволит обновлять статику на CDN с минимальными усилиями.
Включаем сжатие на источнике: Убедитесь, что на источнике статики включено сжатие, как gzip, brotli или другие методы сжатия. Это поможет уменьшить размер передаваемых файлов и улучшить скорость загрузки для пользователей.
Для эффективной оптимизации загрузки веб-приложений важно разбить исходный код на чанки или куски, которые можно загружать по мере необходимости. Это позволяет уменьшить начальную нагрузку страницы и улучшить производительность. Одним из способов сделать это является использование плагина splitChunks, доступного во многих современных сборщиках JavaScript, таких как Webpack.
Когда вы используете splitChunks, сборщик автоматически анализирует ваш код и выделяет общие зависимости в отдельные файлы, которые могут быть загружены асинхронно при необходимости. Это существенно сокращает размер основного бандла и ускоряет загрузку страницы.
Кроме того, для улучшения загрузки страниц вы можете рассмотреть возможность оборачивания отдельных компонентов или страниц в React.LazyLoad. Это позволяет отложить загрузку определенных частей вашего приложения до тех пор, пока они действительно не понадобятся пользователю. Такой ленивый подход к загрузке компонентов также способствует более быстрой загрузке и уменьшению начального объема передаваемых данных.
Оптимизируем тэги link и script
Теги <link rel= "preload">, <link rel= "prefetch">, <link rel= "preconnect"> и <link rel= "prerender">, а также атрибуты <script async> и <script defer> играют важную роль в оптимизации загрузки веб-страниц. Они позволяют контролировать порядок и время загрузки ресурсов, что в свою очередь влияет на производительность и пользовательский опыт.
<link rel= "preload"> используется, когда вы заранее знаете, что определенный ресурс понадобится на странице через некоторое время. Это помогает ускорить загрузку, предвидя будущие запросы.
<link rel= “prefetch”> применяется, когда вы ожидаете, что ресурс будет использован на следующей странице. Это уменьшает задержку при переходе между страницами.
<link rel= “preconnect”> полезен, когда вы знаете, что определенный домен будет использован скоро, но не знаете точный URL ресурса. Это устанавливает соединение с сервером заранее.
<link rel= "prerender"> помогает ускорить отображение страницы, если вы уверены, что пользователи перейдут на нее. Это загружает страницу заранее, чтобы сократить ожидание.
<script async> и <script defer> управляют порядком загрузки и выполнения скриптов. <script async> загружает скрипты параллельно и выполняет их в порядке завершения загрузки. В то время как <script defer> сохраняет порядок скриптов, но загружает их асинхронно и выполняет после загрузки документа.
Важно также учитывать рекомендации к сборке, такие как установка NODE_ENV
в "production"
для оптимизации, избегание избыточных библиотек типа moment и lodash, и проверка возможности поднять целевую сборку ECMA. Дополнительно рекомендуем посмотреть в сторону http2, это новая версия протокола и она значительно ускоряет работу сайтов, причём включается довольно элементарно.
К чему нас это всё привело
Мы проанализировали результаты и вот, что получилось. На изображении ниже я субъективно подсчитал impact от каждой оптимизации, которая внесла свой вклад в снижение метрики LCP. Которая была снижена с 3.5 до 1.5 секунды (по данным LightHouse), но мы не останавливаемся и продолжаем работу в этом направлении. Но когда фронтенд стал отвечать современным стандартам web-разработки, то нужно браться за бекенд и оптимизировать работу самих endpoint’ов, но об этом уже стоит говорить в отдельной статье.
Стало:
Вот так, за 16 часов разработки мы получили прирост более чем в 2 раза, а потратили на это, по сути, ~ 50 рублей (стоимость хранения статики в CDN Селектела в месяц). Бандл main.js удалось сократить с 16 мегабайт до 5.5 килобайт! А стартовая метрика LCP, ради которой и боролись, от 3.5 секунд сократилась до 1.5 секунд. Кроме того, индекс удовлетворенности пользователей сервиса вырос с 6.9 до 8.
Так что, не всегда даже в крупных компаниях нужно изобретать свой велосипед или прибегать к дорогим и зачастую избыточным методам, чтобы решить какие-то проблемы. И такой подход мы исповедуем не только в оптимизации сайтов, но и во всех наших проектах. Если что-то можно сделать просто, то так это и надо сделать. Мы уже писали, как для обработки изображений используем готовые Open Source библиотеки, что позволило нам не тратить время на придумывание своих нейросетей, а очень быстро запустить MVP, протестировать гипотезы и двинуться дальше. Такой метод, разумеется, не лишён минусов и я уверен, что в комментариях нам сейчас расскажут, где и как можно было сделать лучше, что нужно было использовать инструменты ХХХХ и ХХХХ, а также сделать ХХХХ. Разумеется, но в разработке как в математике. Вы можете получить число 4 разными способами: 2+2, 22, 3+1, 1+1+1+1 или даже 2168/(42*(76-74)). Как говорится, при помощи нехитрых приспособлений из буханки хлеба можно сделать троллейбус, но зачем?