Разбираемся с оптимизацией веб-приложений с использованием VueJS и ReactJS (всё, кроме примеров, актуально и для других фреймворков)

# Lighthouse

Lighthouse - это инструмент для оценки основных показателей сайта.
Про Lighthouse есть отличная статья (opens new window), мы же постараемся разобраться, как улучшить показатели сайта.
Также затронем некоторые нюансы оптимизации и неочевидные моменты

# Общие моменты

# View Treemap

По нажатию на данную кнопку мы попадем на страницу с детальной информацией о бандле нашего приложения:

Treemap
Информация о бандле на примере https://doctorpeso.mx/

# View Original Trace

Откроет вкладку производительности в devtools, в которой содержится информация о поведении страницы во время теста:

Treemap

В данном пункте мы можем оценить нагрузку на CPU, падения частоты кадров. Также мы можем увить "тяжелые" задачи, смещение элементов на странице и стадии загрузки страницы (подробнее в разделе Performance)

Ниже пройдемся по каждому пункту и по способам улучшения конретных параметров:

# Performance

Один из важнейших показателей сайта, который включает:

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

# Ленивая загрузка

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

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

// Пример использования на React:

const LazyComponent = React.lazy(() => import("../components/LazyComponent"));

// ...
// Suspense нужен, чтобы отображать различные лоадеры, пока загружается компонент/страница
return (
  <Suspense fallback={<SomeLoader />}>
    <LazyComponent />
  </Suspense>
);
// Пример использования на React (роутинг):

const LazyComponent = React.lazy(() => import("../components/LazyComponent"));

// Можно смело вынести в отдельный файл
const routes = [{ path: '/lazy', component: LazyComponent }];

// ...
// Suspense нужен, чтобы отображать различные лоадеры, пока загружается компонент/страница
return (
  <BrowserRouter>
    <Routes>
      {routes.map(({ path, component }) => (
        <Route key={path} path={path} element={
          <Suspense fallback={<SomeLoader />}>
            {component}
          </Suspense>
        } />
      ))}
    </Routes>
  </BrowserRouter>
);
// Пример использования на Vue3:

import { defineAsyncComponent } from 'vue';

const LazyComponent = defineAsyncComponent({
  loader: () => import('./components/LazyComponent.vue'),
  loadingComponent: SomeLoader,
});
<!-- Пример использования на Vue3: -->

<template>
  <Suspense>
    <template #default>
      <LazyComponent />
    </template>
    <template #fallback>
      <SomeLoader />
    </template>
  </Suspense>
</template>

<script>
import { defineAsyncComponent } from 'vue';

const LazyComponent = defineAsyncComponent({
  loader: () => import('./components/LazyComponent.vue'),
  loadingComponent: SomeLoader,
});
</script>

Использование ленивой загрузки не ограничивается роутингом, таким же образом можно загружать содержимое модальных окон/различных меню или для отображения определенных компонентов по условию

Однако ленивую загрузку не стоит использовать для отделения содержимого, которое пользователь должен увидеть сразу

# Контент приложения

Содержимое приложение напрямую влияет на показатель LСP.

# Текстовое содержимое:

Для того, чтобы браузер мог отобразить тексты до загрузки шрифтов, необходимо добавить следующее свойство:

@font-face {
  font-display: swap;
  /* ... */
}

# Изображения

Для оптимизации загрузки изображений можно использовать:

  • Современные форматы изображений (в частности, .webp)
  • Сжатие слишком больших изображений
  • Ленивую загрузку

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

.webp не поддерживается старыми браузерами, поэтому мы можем добавить копии изображений в классических форматах:

<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="A description of the image." 
    width="300" height="200" loading="lazy" decoding="async">
</picture>

Изображения, которые всё еще слишком много весят, можно сжать (тоже онлайн), однако нужно соблюдать баланс веса и качества изображения, для некоторых изображений (например, для фона) критично хорошее качество

Ленивая загрузка подходит для сайтов, на которых содержится большое количество изображений (например, какя-то лента с изображениями). Браузер будет откладывать загрузку этих изображений, пока эти изображения находятся вне экрана пользователя.

Такой вид загрузки мало подходит для статичных сайтов с небольшим количеством изображений, т.к. может вызвать замедление загрузки картинок при загрузке страницы

Чтобы дать понять браузеру, что изображение должно загружаться лениво, нужно использовать параметр loading="lazy" (также можно использовать параметр decoding="async", для асинхронного декодирования изображения)

<img src="image.jpg" alt="A description of the image." 
    width="300" height="200" loading="lazy" decoding="async">

# Смещение макета

Смещение макета (CLS) - также важная метрика, т.к. при высоком показателе смещения, мы теряем довольно много очков производительности и ухудшаем пользовательский опыт

Чаще всего смещение происходит из-за того, что у изображений нет точной ширины и высоты. Так, если поместить такое изображение в начало страницы, при полной загрузке изображение, оно вызовет смещение контента вниз на высоту этого изображения.

Также смещение может вызывать несоответсвие блока с лоадером и контента при использовании ленивой загрузки. Например, если блок с лоадером имел высоту 500px, а загруженный контент - 700px, то это вызовет смещение окружаюжих блоков на 200px.

Layout shift
Смещение макета во вкладке Experience пункта View Original Trace. При наведении на пункт Related Node будет показан элемент, который был затронут смещением

# Что еще?

Для того, чтобы ускорить загрузку и улучшить показатели производительности, можно использовать сжатие ресурсов (использовать brotli или gzip на сервере nginx или apache)

Не совсем очевидно, но расширения браузера также могут вносить существенный вклад в понижение метрик при тестировании локально, чтобы упростить тестирование, можно добавить в package.json команду "serve": "npx serve -s build". После билда приложения, с помощью этой команды можно развернуть приложение в продакшен-окружении, чтобы не деплоить сайт после каждого изменеия. Также это позволит протестировать продакшен-сборку (на localhost'е показатели будут всегда ниже)

Также значительный прирост в метриках даст добавление на сервер политики кэширования статики (html/css/js или изображенй). Однако прирост будет только при повторной загрузке страницы, когда статика уже хранится в кэше

# Accessibility

Данный пункт включает в себя многие пункты из семантической вёрстки, а также наличия атрибутов alt у изображений, лейблов у инпутов и т.д.

Чтобы улучшить этот показатель, достаточно следовать инструкциям самого Lighthouse по улучшению (у какого элемента чего не хватает)

# Best Practices

Улучшить данную метрику можно, если заменить устаревшие API в приложении (Lighthouse также укажет, что это за API и где оно было использовано)

# SEO

Показатель уровня поисковой оптимизации сайта

Без использования SSR (opens new window), можно улучшить путем добавления мета-тегов в index.html. В некоторых случаях будет указывать на слишком маленький размер элементов для пользователей мобильной версии

# PWA

Слабо актуален для лэндингов, однако очень полезен для функциональных приложений (CRM и т.п.). Позволяет использовать сервис воркер (opens new window) для кэширование всей статики приложения, что позволяет загружать сервис почти моментально (после этого всё будет упираться в скорость ответа API). Также дает возможность обновлять приложение не после обновления страницы (можно настроить и это), а после вызова определенного события (например, по кнопке "Обновить приложение")

PWA доступен для добавления в проект через CLI (Vue cli и CRA)