12 советов по оптимизации веб-приложения

Любое веб-приложение должно работать бесперебойно, а главное, быстро. Это влияет на пользовательский опыт, повышает конверсию... а если перевести с маркетингового на человеческий — делает пользователей счастливее. В этой статье мы поделимся лайфхаками, которые помогают приложению работать быстрее.

Какие-то из них могут показаться очевидными, но от этого они не становятся менее эффективными.

12 советов по оптимизации веб-приложения

HTML

1. Изображения уменьшенного размера

Без картинок не обходится ни один продукт. При этом одна картинка высокого разрешения может весить столько же, сколько весь бандл приложения. А если таких картинок много?

Обилие неоптимизированных картинок может заметно утяжелять и замедлять страницу
Обилие неоптимизированных картинок может заметно утяжелять и замедлять страницу

Чтобы уменьшить вес медиа, используйте файлы необходимых размеров — не больше. Если требуется картинка 200*300px — нет никакой нужды использовать разрешение 4k.

2. Lazy loading для картинок вне пределов окна браузера

Взгляните ещё раз на картинку в первом пункте👆 Пользователь не видит изображения за границей экрана. Не стоит тратить ресурсы на загрузку всех изображений сразу. Лучшая практика — загружать картинки по мере необходимости, когда пользователь доскроллит до них. Это называется «ленивая загрузка», lazy loading.

Реализовать это решение помогают специальные плагины, например, для jQuery или для Vue.

3. Async и defer при подключении

Раньше рекомендовали подключать js-файлы в конец страницы, чтобы не мешать её отрисовке, ведь выполнение кода блокирует поток. В современных браузерах атрибут async выполняет скрипты сразу после загрузки, а defer — после завершения разбора HTML.

Следовательно, если порядок выполнения скриптов важен, то лучше использовать атрибут defer, так как он гарантирует, что скрипты будут выполняться в том же порядке, в котором появляются на странице. Если же порядок выполнения скриптов не имеет значения, используйте атрибут async — он может ускорить загрузку страницы за счёт параллельной загрузки и выполнения скриптов.

4. Cache-Control

Этот метатег позволяет управлять кешированием страницы. Он выглядит так: <meta http-equiv="Cache-Control" content=""/>. Вот возможные значения этого метатега с расшифровкой:

  • public — документ кешируется во всех доступных кешах
  • private — кешируется браузером, не кешируется прокси-сервером
  • no-cache — браузер и прокси-сервер не кешируют документ
  • no-store — может быть кеширован, но не сохраняется в архиве
  • max-age=time, must-revalidate — указывает браузеру, сколько секунд хранить документ в кеше

  • max-age=time, proxy-revalidate — указывает прокси-серверу, сколько секунд хранить документ в кеше

Выбор конкретного значения зависит от того, как будет вести себя контент на странице. Если это страница со статичным контентом, который не должен меняться — можно установить значение public. Если это страница, данные на которой меняются в реальном времени — разумно установить no-cache. Чаще всего используется кэш max-age=3600, must-revalidate.

5. Lighthouse

На самом деле рекомендации по полноценной html-оптимизации могут потянуть на небольшую книгу. Или пять. Чтобы не держать всё в голове, можно использовать прекрасный инструмент от Google — Lighthouse. Это расширение для Chromium-based браузеров, но в последних версиях Chrome он существует прямо в devtools:

Создание отчёта Lighthouse в инструментах разработчика
Создание отчёта Lighthouse в инструментах разработчика

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

JS

6. Тяжелые операции на фронте — не ок

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

7. …не используйте chaining для array без необходимости

В коде нередко встречаются такие конструкции:

let myArray = []

const newMyArray = myArray.map(value => mapFunctionForMyArray(value))

.filter(value => filterFunctionForMyArray(value))

Выглядит симпатично, и сразу понятно, что происходит в этой функции, правда? На самом деле это два цикла по одним и тем же данным, которые можно заменить одним перебором, например через forEach.

Впрочем, это не всегда актуально. С использованием, например, одного forEach код станет менее очевидным и понятным. Если myArray состоит из десятка элементов, этот совет можно проигнорировать, но если данных много — то преимущество будет в скорости обработки операции большого массива данных.

Отдельно стоит сказать про spread оператор. Его часто используют для копирования массивов или объектов. Но не все знают, что const newArray = [...oldArray] — это на самом деле тоже цикл по newArray. Не используйте его бездумно, особенно во вложенном формате.

8. WebWorker для выполнения ресурсоёмких вычислений

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

Для Vue 3 я использую функцию useWebWorkerFn из VueUse. Он позволяет легко выполнять задачу в отдельном потоке. В него можно передавать любые примитивы JS, но есть нюанс — у него другой контекст и в него нельзя передавать:

  • DOM-элементы.
  • Функции и сложные объекты, например, классы с методами.
  • DOM-API.
  • Window, document, и другие свойства глобального объекта браузера.

9. Wasm для очень тяжёлых задач

Если у вас есть сложная и ресурсоёмкая задача, пункт 6 не сработал, пункт 8 помог, но всё ещё медленно — можно попробовать WebAssembly (wasm). Например, в задачах, связанных с большими данными, это позволяет получить значительный прирост производительности, хоть и не всегда. В wasm компилируется код на многих языках — вот, например, гайд по Go.

10. Виртуальный скролл для объектов с большим количеством элементов

Если у вас на странице есть контейнер со списком внутри, длинная таблица или что-то похожее — используйте виртуальный скролл. Он даёт ощутимый прирост в производительности. Я, например, использую virtual-scroll.

11. Вкладка Performance в devtools

Она поможет определить узкие места при выполнении скрипта. Это must-have для профилирования и оптимизации вашего кода. Описание возможностей этой вкладки достойно отдельной статьи, поэтому просто оставлю ссылку на хороший гайд.

12. При использовании фреймворков не делайте реактивными данные, которые в этом не нуждаются

При работе с Vue, React и других фреймворков дважды подумайте над тем, нужна ли вам реактивность конкретной переменной. А если это большой массив — подумайте трижды. Глубокая реактивность запускается при изменении любого элемента, в том числе и дочернего. Это может вызвать проблемы с производительностью из-за большого количества зависимостей, а также привести к проблемам с синхронизацией. В таком случае стоит использовать, например, computed-свойства относительно ко VUE. Это помогает минимизировать избыточные обновления.

Более того, если переменная является объектом/массивом, и она не будет изменяться в будущем — лучше обернуть её в Object.freeze, это даст преимущество — замороженные объекты оптимизируются движками JavaScript, так как их структура и значения остаются неизменными. Это позволяет экономить ресурсы на внутренних проверках.

Арсений Адамян
Руководитель группы фронтенд-разработки arcsinus
Начать дискуссию