Chrome Audit на 500: Часть 1. Лендинг

в 16:51, , рубрики: javascript, optimization, performance, seo, TypeScript

В инструментах разработчика браузера хром есть вкладка «Audit». На ней расположился инструмент который называется Lighthouse, служит он для анализа насколько хорошо сделано веб приложение.

image

Недавно я решил протестировать одно приложение и ужаснулся результатам. Сразу по нескольким разделам оценка находилась в красной зоне. Я принялся изучать что же с моим приложением не то. И нашел в результатах анализа большой список очень полезных рекомендаций, выполнил их и получил 500 баллов. В результате приложение стало запускаться значительно быстрее, а я пересмотрел несколько концепций относительно метода построения приложений. А в этой статье я хочу поделиться самыми интересными решениями к которым я пришел.

Если у вас нету возможности установить хром, то можно поставить lighthouse из npm и работать с ним из консоли.

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

Performance

Выбор сервера

Это самый банальный совет, но именно это является фундаментом для всей производительности. К счастью найти хорошее решение просто, это любой ЦОД уровня Tier 3 или Tier 4. Сам этот статус ничего не говорит о скорости, он говорит о том что владельцы позаботились о качестве.

Инициализация приложения

Когда то в браузерах был только html. Потом появился javascript и бизнес логика. Сегодня логики на клиенте стало настолько много, что html с ней не справляется и стал вовсе не нужен. Но, т.к. браузер не может начать загружаться из JavaScript файла, нам придется разместить небольшой кусок html для запуска нашего приложения.

В идеале он должен выглядеть примерно так:

<!DOCTYPE html>
<html lang="ru">
<head>
    <title>Название сайта</title>

    <link rel="manifest" href="./manifest.webmanifest">
    <link rel="shortcut icon" href="content/images/favicon.ico" type="image/x-icon">

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width" />
    <meta name="theme-color" content="#425566">
    <meta name="Description" content="описание сайта">
</head>
<body>
    <div id="loader">
        loading
    </div>
    <script async>
        // todo: попозже добавим скрипты загрузки
    </script>
</body>
</html>

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

В данной статье не рассматриваем оптимизацию для ботов, но скажу что проще всего отловить конкретного бота и отдать то что конкретному боту нужно. Бот гугла сам всё поймет из контента который подгрузится позже.

Использовать сплеш скрин

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

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

image

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

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

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

Инициализация приложения

Теперь, когда мы отвлекаем внимание пользователя картинками, настало время загрузить приложение. Для этого мы внутрь блока script вставим следующий скрипт.

// 1. Подключаем ServiceWorker, подробнее рассмотрим в блоке PWA
if (navigator.serviceWorker && !navigator.serviceWorker.controller) {
    navigator.serviceWorker.register('pwabuider-sw.js', { scope: './' });
}

// 2. Подключаем необходимые стили
[
    "./content/font.css",
    "./content/grid.css"
].forEach(function(url){
    var style = document.createElement("link");
    style.href = url;
    style.rel = "stylesheet";
    document.head.appendChild(style);
});

// 3. Подключаем необходимые скрипты
[
    "./scripts/polyfills.min.js", // или vendors.min.js
    "./scripts/main.min.js" // spa приложение
].forEach(function(url){
    const script = document.createElement("script");
    script.src = url;
    script.async = false;
    document.head.appendChild(script);
});

Из чего он состоит:

  1. Подключение PWA — рассмотрим в соответствующем разделе ниже. Подключать его надо как можно раньше, потому что возможно в pwa уже будет все необходимое для работы сайта и запросов на сервер больше не будет.
  2. Подключение стилей — по мере необходимости подключаем стили. В идеале этого кода вообще не должно быть и стили должны подключать ваши компоненты по мере необходимости.
  3. Подключение скриптов — подключаем программу. Состоять он должен всего из двух этих скриптов. Все остальные скрипты (карты, аналитика, библиотеки) не влияющие на отображение первого экрана (не всей страницы) загружаются уже после отрисовки первого экрана приложения. Аналитику уже должен подгрузить компонент аналитики после загрузки программы. Качество аналитики от этого не пострадает, а системы аналитики поддерживают загрузку после загрузки программы. Карты должны погрузиться только после того как пользователь до них доскролит и они попадут в экран. Со сторонними библиотеками, необходимых для работы конкретных компонентов, аналогично.

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

Ленивая подгрузка и отрисовка

Очень важным параметром является то насколько быстро отрисуется первый экран и пользователь сможет начать взаимодействовать с этой страницей. И здесь стоит использовать следующие оптимизации:

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

Хорошим решением тут может служить компоненты lazy-block и lazy-img:

<div>
   <p>текст</p>
   <lazy-img src="..."/>
</div>
<lazy-block>
   тяжелые блоки
</lazy-block>
<lazy-block>
   тяжелые блоки
</lazy-block>
<lazy-block>
   тяжелые блоки
</lazy-block>

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

Но не стоит забывать и о гугл боте, который видит spa, но не скролит всю страниу. Поэтому если вы не позаботитесь, то он не увидит ваш контент.

2. Если какой либо из компонентов использует внешнюю зависимость, он должен будет загрузить ее сам по мере необходимости. Например это может быть блок с картами, графиками или 3D графикой. А с недавних пор в JS появился способ сделать это очень просто:

class Demo {
    constructor() {
        this.init();
    }
    private async init() {
        const module = await import('./external.mjs'); // динамический импорт
        module.default();
        module.doStuff();
    }
}

В результате пользователь подгружает только то что ему надо, что сильно экономит ресурсы пользователя и сервера.

Минимизация бандла

И… да, вы подумали не о том, речь не о минификации в Terser (UglifyJS), а о том что бы конкретному браузеру отдавать только то что ему нужно.

Дело в том что браузеры постоянно развиваются, у них появляется новое API, разработчики начинают использовать его, а для совместимости со старыми браузерами подключают полифиллы и транспиллеры. В итоге образуется проблема что пользователи с новейшими браузерами, которых около 80%, получают код который предназначен для пользователей IE11, транспиленный и с полифилами.

Проблема этого кода в том что он содержит много лишнего текста, а производительность его в 3 раза меньше (по моим субъективным оценкам) чем оригинала. Гораздо логичнее делать несколько бандлов для разных версий браузеров. Бандл с ES2017 кодом для Chrome 73 с минимумом полифилов, бандл с ES5 для IE11 с максимум полифилов и т.д.

О том как за один раз собрать бандлы разных версий я писал в предыдущей статье. А для выбора правильной версии в браузере немного модифицируем скрипт подключения программы:

var esVersion = ".es2017";
try{
    eval('"use strict"; class foo {}');
}catch(e){
    esVersion = ".es5";
}

[
    "./scripts/polyfills" + esVersion + ".min.js",
    "./scripts/main" + esVersion + ".min.js"
].forEach(function(url){
    const script = document.createElement("script");
    script.src = url;
    script.async = false;
    document.head.appendChild(script);
});

В итоге пользователи современных браузеров получат максимально легкую и производительную программу, а пользователи IE11 получат то что они заслужили.

Еще один интересный способ минификации

Очень интересная библиотека для сокращения бандла на 50%, к сожалению с непредсказуемым результатом.

Минимизация кода

Очень популярна проблема когда разработчики начинают подключать всё на что упадет их взгляд. В результате иногда можно наблюдать программы которые весят по 5-15 мб и даже больше. Поэтому к выбору библиотек надо подходить с умом.

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

Избегайте использования тяжелых библиотек. Вместо использование таких библиотек как jquery, lodash, moment, rxjs и любая другая в минифицированном размере >100кб, постарайтесь поглубже изучить алгоритмы и найти решение на нативном JS. Как правило на нативном скрипте можно написать проще, а вы избавляетесь от лишней тяжелой зависимости.

Минификация картинок

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

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

Для использования Sharp необходимо написать скрипт, я использую его в связке с glob для рекурсивного поиска изображений в директории с исходными картинками, а сам скрипт прячу за утилитой запуск задач gulp. Пример моей сборки:

gulp.task('core-min-images', async () => {
    const fs = require('fs');
    const path = require('path');
    const glob = require('glob');
    const sharp = require('sharp');

    // 1. Рекурсивно получаем список файлов для обработки при помощи утилиты glob
    const files = await new Promise((resolve, reject) => {
        glob('src/content/**/*.{jpeg,jpg,png}', {}, async (er, files) => {
            !er ? resolve(files) : reject(er);
        });
    });

    // 2. Запускаем процесс обработки списка изображений
    let completed = 1;
    await Promise.all(files.map(async (file) => {

        const outFile = file.replace(/^src/, 'www');
        const outDir = path.dirname(outFile);

        // 2.1. Проверяем наличие дирректории для обработанных файлов
        if (!fs.existsSync(outDir)) {
            fs.mkdirSync(outDir, { recursive: true });
        }

        // 2.2. Считываем исходное изображение
        const origin = sharp(file);

        // 2.3. Генерируем изображение в разрешении 1920 по горизонтали с сохранением
        // пропорций и сохраняет в формат в jpg/png и webp с дефолтным качеством (80%)
        const size1920 = origin.resize({ width: 1920 });
        await size1920.toFile(outFile.replace(/.(jpeg|jpg|png)$/, '-1920w.$1'));
        await size1920.toFile(outFile.replace(/.(jpeg|jpg|png)$/, '-1920w.webp'));

        // 2.4. Аналогично для разрешения 480 по горизонтали
        const size480 = origin.resize({ width: 480 });
        await size480.toFile(outFile.replace(/.(jpeg|jpg|png)$/, '-480w.$1'));
        await size480.toFile(outFile.replace(/.(jpeg|jpg|png)$/, '-480w.webp'));

        // 2.5. Аналогично для разрешения 120 по горизонтали
        const size120 = origin.resize({ width: 120 });
        await size120.toFile(outFile.replace(/.(jpeg|jpg|png)$/, '-120w.$1'));
        await size120.toFile(outFile.replace(/.(jpeg|jpg|png)$/, '-120w.webp'));

        // 2.6. Это для более интересных логов
        console.log(`Complete image ${completed++} of ${files.length}:`, file);
    }));

});

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

<img src="sample.jpg"/>

То теперь надо писать так:

<picture>
    <source srcset="img/sample-480w.webp" type="image/webp">
    <source srcset="img/sample-480w.jpg" type="image/jpeg"> 
    <img src="img/sample-480w.jpg" alt="Альтернативный текст!">
</picture>

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

<picture>
    <source srcset="img/sample-480w.webp, img/sample-960w.webp 2x" type="image/webp">
    <source srcset="img/sample-480w.jpg, img/sample-960w.webp 2x" type="image/jpeg"> 
    <img src="img/sample-480w.jpg" alt="Альтернативный текст!">
</picture>

А с учетом того что теперь можно генерировать картинки на этапе сборки приложения, получается на все картинки будет одинаковый набор форматов и разрешений, а значит мы можете унифицировать эту логику и скрыть за каким нибудь компонентом, например тем же <lazy-img src="img/sample.jpg">.

Минификация стилей

Загружайте только те стили которые используют ваши компоненты. В идеале когда стили привязаны компонентам, и встраиваются в дом только когда рисуется сам компонент.

Минимизируйте название классов. Огромная длинна вложенных или БЭМ селекторов в стилях плохо влияют на размер вашего приложения. В настоящее время полно инструментов которые на генерируют стили с уникальными селекторами: JSS, Styled Components, CSS Modules.

Минификация дома

Все мы знакомы с html, но мало кто задумывался что это всего лишь простая абстракция над деревом очень сложным объектов. Цепочка наследования для элемента div выглядит следующим образом:

HTMLDivElement -> HTMLElement -> Element -> Node -> EventTarget

И у каждого объекта в этой цепочки от 10 до 100 свойств и методов которые потребляют много памяти. И все это богатство должен учитывать движок DOM для построения картинки которую мы видим. Поэтому старайтесь не использовать лишние элементы в доме.

Минифицируйте HTML. Удаляйте все что вы используете для форматирования html на этапе написания. Дело в том что пробелы которые используются при написании кода, в браузере также превращаются в объекты дома:

TextNode -> Node -> EventTarget

Удаляйте комментарии. Они также являются элементом дома и потребляют много ресурсов:

Comment -> CharacterData -> Node -> EventTarget

Хорошей практикой может служить использование шаблонизаторов на jsx. Дело в том что при компиляции он превращается в нативный js код, который не генерирует пробелов, комментариев и никогда не ошибается в открытии и закрытии тэгов.

Плохой практикой, я бы даже сказал кошмаром, может служить сайт facebook.com. Приведу фрагменты html:

Фрагмент страницы html

<!-- Фрагмент 1 -->
<div class="">
  <div class="_42ef">
    <div class="_25-w">
      <div class="_17pg">
        <div class="_1rwk">
          <form class=" _129h">
            <div class=" _3d2q _65tb _7c_r _4w79">
              <div class="_5rp7">
                <div class="_1p1t">
                  <div class="_1p1v" id="placeholder-77m1n" style="white-space: pre-wrap;">
                    Напишите комментарий...
                  </div>
                </div>
              </div>
            </div>
            <ul class="_1obb">
              ...li...
            </ul>
          </form>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Фрагмент 2 -->
<div>
  <div>
    <div class="_3nd0">
      <div class="_1mwp navigationFocus _395 _4c_p _5bu_ _34nd _21mu _5yk1" role="presentation" style="" id="js_u">
        <div class="_5yk2" tabindex="-1">
          <div class="_5rp7">
            <div class="_1p1t" style="">
              <div class="_1p1v" id="placeholder-6t6up" style="white-space: pre-wrap;">
                Что у вас нового?
              </div>
            </div>
            <div class="_5rpb">
              <div aria-autocomplete="list" aria-controls="js_1" aria-describedby="placeholder-6t6up"
                aria-multiline="true" class="notranslate _5rpu" contenteditable="true"
                data-testid="status-attachment-mentions-input" role="textbox" spellcheck="true"
                style="outline: none; user-select: text; white-space: pre-wrap; overflow-wrap: break-word;">
                <div data-contents="true">
                  <div class="" data-block="true" data-editor="6t6up" data-offset-key="6b02n-0-0">
                    <div data-offset-key="6b02n-0-0" class="_1mf _1mj">
                      <span data-offset-key="6b02n-0-0">
                        <br data-text="true">
                      </span>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Как видим используется вложенность из десяти элементов, но эта вложенность не выполняет никакой работы. Первый фрагмент всего лишь выводит текст «Напишите комментарий...» и иконки, второй «Что у вас нового?». В результате такого не рационального использования DOM вся производительность шаблонизатора React просто сводится на нет, и сайт становится одним из самых медленных что я знаю.

Progressive Web App

Файл манифеста

PWA позволяет пользоваться вашим веб приложение как нативным приложением. При включении поддержки на сайте в меню браузера появляется кнопка инсталляции вашего сайта на устройство (Windows, Android, iOS), после чего оно начинает вести себя как нативное и работать в режиме оффлайн, и всё это в обход магазинов приложений.

Включить на сайте поддержку PWA на самом деле очень просто. Достаточно включить в html страницы ссылку на файл манифеста. Файл манифеста можно сгенерировать на сайте pwabuilder.com.

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

Сервис воркер

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

Пример кода можно посмотреть там же на pwabuilder.com:

// This is the service worker with the Cache-first network

const CACHE = "pwabuilder-precache";
const precacheFiles = [
  /* Add an array of files to precache for your app */
];

self.addEventListener("install", function (event) {
  console.log("[PWA Builder] Install Event processing");

  console.log("[PWA Builder] Skip waiting on install");
  self.skipWaiting();

  event.waitUntil(
    caches.open(CACHE).then(function (cache) {
      console.log("[PWA Builder] Caching pages during install");
      return cache.addAll(precacheFiles);
    })
  );
});

// Allow sw to control of current page
self.addEventListener("activate", function (event) {
  console.log("[PWA Builder] Claiming clients for current page");
  event.waitUntil(self.clients.claim());
});

// If any fetch fails, it will look for the request in the cache and serve it from there first
self.addEventListener("fetch", function (event) { 
  if (event.request.method !== "GET") return;

  event.respondWith(
    fromCache(event.request).then(
      function (response) {
        // The response was found in the cache so we responde with it and update the entry

        // This is where we call the server to get the newest version of the
        // file to use the next time we show view
        event.waitUntil(
          fetch(event.request).then(function (response) {
            return updateCache(event.request, response);
          })
        );

        return response;
      },
      function () {
        // The response was not found in the cache so we look for it on the server
        return fetch(event.request)
          .then(function (response) {
            // If request was success, add or update it in the cache
            event.waitUntil(updateCache(event.request, response.clone()));

            return response;
          })
          .catch(function (error) {
            console.log("[PWA Builder] Network request failed and no cache." + error);
          });
      }
    )
  );
});

function fromCache(request) {
  // Check to see if you have it in the cache
  // Return response
  // If not in the cache, then return
  return caches.open(CACHE).then(function (cache) {
    return cache.match(request).then(function (matching) {
      if (!matching || matching.status === 404) {
        return Promise.reject("no-match");
      }

      return matching;
    });
  });
}

function updateCache(request, response) {
  return caches.open(CACHE).then(function (cache) {
    return cache.put(request, response);
  });
}

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

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

Accessibility

Корректность атрибутов для людей с особыми потребностями

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

  • Используйте достаточно контрастные цвета. По статистике Минздрава 20% людей имеют проблемы со зрением. А плохой контраст сайтов только усложняет им жизнь, а здоровым людям увеличивает утомляемость.
  • Расставте tabindex. Позвольте пользоваться сайтом без мышки и сенсорных устройств. Грамотное расположением переходов с помощью клавиатуры сильно упрощает процесс заполнения форм.
  • Атрибут aria-label на ссылках. Позволяет экранным дикторам зачитывать текст внутри атрибута.
  • Атрибут alt на картинках. Аналогично предыдущему. Кроме того отобразит текст в случае невозможности загрузки картинки.
  • Язык документа. Пометьте тег html атрибутом с языком lang=«код языка». Это поможет вспомогательным инструментам правильно настроиться на работу.

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

Best Practices

Отделите фронтенд приложение о серверного приложения

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

Во вторых отделите ваше клиентское SPA приложение от бекенд приложения. Вы же не держите вместе серверное приложение и windows приложение, и андройд приложение, и iOS приложение. Вот и веб приложение уже давно является самодостаточным приложением, которое может работать и без сервера и даже в режиме оффлайн. Самая популярная ошибка что я вижу это когда бекенд фреймворк вроде Spring или Asp.Net занимаются раздачей статики, в том числе собранным SPA приложением. Давно пора перестать так делать и вынести статику и SPA в отдельный микросервис и спрятать за специализированным веб сервером для раздачи статики, например nginx.

image

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

Настройка прокси сервера, HTTP/2, gzip, cache

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

  • Настройка сертификата SSL. На самом деле установка SSL соединения относительно дорогое удовольствие, но необходимое, так что сделать его лучше на легковесном и быстром прокси сервер, например Nginx. А вот Nginx и Asp.Net Core умеют переиспользовать между собой имеющиеся соединения, что сильно экономит ресурсы.
  • GZIP Включите и настройте сжатие ответов клиенту. Текст хорошо сжимается в десятки раз что экономит время и трафик клиенту.
  • Cache Закешируйте все сжатые ответы. Также можно кешировать все Get, Head запросы к бекенду, что на порядок снижает на него нагрузку.
  • Настройте роутинг и балансировку Грамотно распределите нагрузку между бекенд приложениями.

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

SEO

Создайте мета теги в html и используйте семантическую разметку

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

Конец

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

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

Автор: LabEG

Источник

* - обязательные к заполнению поля