preview.jpg
preview.jpg

Быстрый сайт - что-то очень очевидное и простое - сайт загружается быстро и не зависает. “3 секунды ожидания и вы начинаете терять пользователей” - пожалуй это правило слышал любой веб-разработчик. Но это правило лишь вершина айсберга - как в вопросе причин потери клиентов, так и в реальном результате.

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

Факторы производительности

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

И пожалуй, правило трёх секунд, сегодня уже нельзя назвать самым главным и единственно верным. Сейчас большинство пользователей имеют достаточно быстрый интернет, чтобы загрузить даже крайне тяжёлую страницу за пару секунд. Дальнейшие оптимизации скорости загрузки проводят всего для оставшихся 20% клиентов. Но есть факторы которые влияют на все 100% клиентов.

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

Пользовательский опыт

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

Очень очевидные случаи, но ими грешит чуть ли не каждый третий сайт. Но есть и менее очевидные случаи.

Отклик сайта

Представьте два блога. В обоих вы переходите на вторую страницу списка статей. В первом случае ничего не происходит. Секунда, две... вы нажимаете ещё раз. Ещё секунда и вот долгожданный переход на новую страницу. В следующий раз при переходе вы, весьма вероятно, нажмёте повторно уже через секунду. Потом повторные клики будут сразу. И причина тому проста - интерфейс не даёт никакого отклика о том, что действие прошло успешно. Своеобразный “эффект CTRL+C”.

ctrl_c_effect.jpg
Источник: Reddit

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

Но как поведут себя сайты при той же ситуации и интернете 5G.

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

В данном случае оптимальным решением будет показ лоадера или скелетонов с небольшой задержкой - обычно в районе 150-300мс. Через секунду можно показать дополнительное сообщение формата “страница грузится дольше обычного, пожалуйста, подождите”. Такое решение используется, например в Jira и Notion. Обычно пользователи не замечают эти хитрости (а именно это, как говорилось прежде, главный фактор быстрого сайта).

Другое возможное решение это прогресс-бар. В таком случае сразу отображается линия показывающая процент загрузки. Этот процент может быть и символичным - двигаясь по шагам (начал действие - на 10%, отправил запрос - на 20%, получил ответ - на 90%). Такое решение можно встретить, например, в GitHub.

Показ контента

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

Например, если на странице есть попап с карточками связанных задач - не стоит ждать прогрузки всех задач и только после этого открывать попап. Вполне возможно пользователю и вовсе не нужен этот список. Гораздо лучше сразу отобразить окно с известными данными и показать что задачи ещё подгружаются (упомянутыми прежде способами).

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

Анимации

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

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

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

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

Показ изображений

Ещё один базовый и спорный пример - ступенчатая загрузка изображений. Когда изображение сперва отображается в очень плохом качестве с блюром, а в фоне загружается полноценное. Так работает например mirage от Cloudflare или Image от Next.js. Спорно это решение по нескольким причинам:

  1. Это борьба со следствием. Если на сайте тяжёлые изображения - нужно их оптимизировать, перевести в разные форматы, сжать, уменьшить и т.д. (детальнее будет разобрано позже);

  2. Современные браузеры прекрасно знают как и когда грузить изображения. Используя JS эта ответственность берётся на себя. И очень сложно повторить все необходимые оптимизации.

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

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

Ещё одна возможная ошибка изображений - установить loading="lazy" крупному изображению в первой секции. Оно будет загружаться после всех скриптов, спрайтов, файлов из CSS, других изображений без этого атрибута и т.д. А это изображение пользователь должен увидеть сразу, а значит и в очереди оно должно стоять как можно выше.

При этом я нарочно не упоминаю в этой части предзагрузку изображения (meta preload). Так как гораздо лучше грузить изображения через picture с разными форматами, а с preload нужно либо выбирать предпочтительный, либо грузить все возможные варианты (что также повредит всем остальным метрикам).

Поздняя загрузка изображений также отразится и на метриках, а именно на LCP. Также неправильно настроенные изображения могут дополнительно ухудшить CLS. Собственно на этом и перейдём к главному набору метрик - Web Vitals.

Web Vitals

Web Vitals - это набор метрик, которые, по мнению Google, являются ключевыми для комфорта пользователя. Среди них на данный момент:

  • FCP (First Contentful Paint / первая отрисовка контента);

  • LCP (Largest Contentful Paint / отрисовка самого крупного контента);

  • TBT (Total Blocking Time / суммарное блокирующее время);

  • CLS (Cumulative Layout Shift / cкачки интерфейса);

  • SI (Speed Index / индекс скорости);

  • INP (Interaction to Next Paint / взаимодействие с последующей отрисовкой).

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

Сам список постоянно меняется. Например, в 2017 году были метрики First Meaningful Paint вместо FCP, Perceptual Speed Index вместо Speed Index, Estimated Input Latency (который затем был заменён на First Input Delay и сейчас заменяется на INP).

lighthouse_1_5_0.png
Версия LightHouse от 2017 года

Метрики Web Vitals являются частью анализатора LightSpeed. Тот в свою очередь является частью сервиса PageSpeed Insights. А он является частью инструментария Google PageSpeed. Последний был представлен компанией Google в далёком 2010 году.

image.png
Версия pagespeed labs от 2011 года - https://youtu.be/_MuVoabSLeY?si=wgEGtAMiQXIMkRNZ&t=2593

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

Причины появления Web Vitals

Если сегодня найти какой-нибудь сайт первого поколения - содержимое будет получено крайне быстро. Даже несмотря на то, что у этих сайтов не было никаких оптимизаций (даже сжатия ответа). Например netside.net — суммарный вес загружаемых ресурсов - 117кБ (передаётся 114кБ). Для сравнения вес ресурсов средней страницы в Wikipedia - 750кБ (передаётся 250кБ), вес ресурсов страницы react.dev/reference/react - 2.1МБ (передаётся 915кБ).

При этом и Wikipedia и документация React при быстром интернете грузятся примерно столько же как netside, при медленном же в 2-3 раза дольше на отображение всего контента (8 секунд против 3 секунд).

image.png
Загружаемые ресурсы на странице Wikipedia - https://en.wikipedia.org/wiki/Northern_Hemisphere

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

Пока одни делали огромные сервисы и оптимизировали их до загрузки в пару секунд, другие просто масштабировали сайты до десятков и сотен мегабайт. JQuery, Angular и позднее Vue с React разогнали этот тренд до невероятных скоростей.

Так, пользователи стали часто попадать на сайты, которые грузятся дольше 3 секунд. Не дождавшись контента они начинали выходить в поиске других сайтов. В какой то момент это заметил и Google. Ведь разочаровавшись так несколько раз, пользователи могли закрыть поисковик или уйти смотреть в другом (в котором выдача формировалась совсем иначе).

В 2020 году был полноценно представлен набор метрик Web Vitals, произошёл ребрендинг LightHouse, начался новый этап в мире оптимизаций. Метрики вернулись в веб с новой силой.

Влияние метрик на бизнес

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

image.png
Рекомендации LightHouse

Теперь метрики играют важную роль в выдаче. Медленные сайты рискуют не только правилом трёх секунд, но и потерей естественного трафика, опускаясь в выдаче. Но почему?

Сам Google, конечно, не рассказывает о всех правилах выдачи, говоря что попросту их не знает. Но, известно, что место в выдаче является суммой множества факторов. Некоторые факторы более приоритетные, некоторые менее. Конечно же наполнение сайта, соответствие запросу и его “вес” - являются одними из наиболее важных. Но метрики web vitals также являются одним из факторов выдачи.

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

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

image.png
“You know, I’m something of a scientist myself”

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

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

Ещё несколько ресурсов о пользе оптимизации метрик для бизнеса:

Метрики для динамических сайтов

Постепенно ряд библиотек и фреймворков одностраничных приложений (SPA) начали использоваться для построения маркетинговых сайтов. Из-за этого огромная доля контента оказалась вне зоны доступа поисковых систем. В очередной раз на это среагировал Google. Поисковые роботы Google стали не просто сразу сканировать сайты, но и давать отрендериться динамическому контенту. Теперь приложения стали полноценно участвовать в борьбе за место в выдаче.

image.png

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

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

Сегодня серверные компоненты React.js меняют ситуацию. В комплексе со множеством встроенных оптимизаций React.js и Next.js - они позволяют получить из коробки сравнимые метрики и большой потенциал для маркетинговых целей. Но это тема для отдельных статей.

Сбор и анализ

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

LightHouse

Первым инструментом в этом списке будет уже упомянутый LightHouse.

LightHouse - основной инструмент для работы с Web Vitals в лабораторных условиях. Он позволяет собрать сами метрики, посмотреть дополнительные факторы (такие как a11y или лучшие практики), найти слабые моменты и разобрать рекомендации по улучшениям.

image.png
Рекомендации LightHouse

Проверить сайт в LightHouse можно как локально, так и через сервис PageSpeed Insights - на рекомендуемом устройстве.

Если нужно быстро проверить сайт на основные ошибки - будет достаточно открыть локальную версию (Developer tools → LightHouse → Analyze Page Load). Если же нужен полноценный анализ, отслеживание динамики и более стабильные показатели - стоит воспользоваться PageSpeed Insights. Сервис покажет всю нужную информацию, но именно на рекомендованном устройстве. Также, если сайт достаточно популярен - сервис покажет средние метрики по пользователям.

image.png
Web Vitals метрики среди реальных пользователей из PageSpeed Insights

Developer tools

Помимо LightHouse в DevTools есть ряд других полезных вкладок, но наиболее полезными для оптимизаций, пожалуй, будут Performace и Network.

Performance

Performance позволяет записывать нагрузку страницы, включая загрузку ресурсов, выполнение скриптов и рендеринг.

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

devtools_perf_results.png
Пример сканирования во вкладке Performance

Network

Во вкладке Network можно увидеть все сетевые запросы, включая загрузку ресурсов (изображений, скриптов, стилей) и REST-запросы. Это помогает определить, какие ресурсы замедляют загрузку.

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

devtools_net_wiki.png
Загружаемые ресурсы на страницу Wikipedia - https://en.wikipedia.org/wiki/Northern_Hemisphere

Search Console

Немного дополнительной информации о метриках среди реальных пользователей можно получить из Search Console.

image.png
Web Vitals показатели из Search Console

Google Analytics

Возможно неожиданный сервис в этом списке, но один из наиболее эффективных для анализа результатов реальных пользователей (Real User Monitoring / RUM). GA позволяет собирает любые данные просто включив нужные пункты в настройках, отправив дополнительные метрики вручную или же подключив уже готовый пресет в GTM (google tag manager). Для Web Vitals тоже имеется готовый пресет. Подключив его, в GA начнёт отправляться вся нужная информация о метриках.

Остаётся лишь построить графики и можно начинать отслеживать тенденции.

ga_lcp.png
Пример графика LCP по данным из Google Analytics

Сторонние сервисы

DebugBear

DebugBear позволяет автоматически собирать и анализировать метрики через несколько инструментов, включая LightHouse, проводить анализ в несколько потоков (чтобы собрать более точные метрики), отслеживать и сравнивать метрики и запросы в течении длительного времени.

Speed Curve

Speed Curve позволяет анализировать метрики среди реальных пользователей, тестировать из разных браузеров и локаций, проверить производительность на уровне CI/CD, анализировать INP и многое другое.

GTmetrix

GTmetrix позволяет анализировать в разных условиях, воспроизведить видео-записи, тестировать в разных сценариях, анализировать каскадные диаграммы запросов.

Site24x7

Site24x7 позволяет мониторить все составляющие сервера - от скорости загрузки сайта до мониторинга сети и kubernetes из более чем 130 локаций по миру

Pingdom

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

Uptrends

Uptrends предлагает мониторинг доступности, производительности и инфраструктуры. Включает анализ из разных локаций, детальные отчёты, тестирование сценариев, мониторинг API.

WebPageTest

WebPageTest позволяет анализировать сайт и впоследствии проверять оптимизации с помощью динамических тестов. Также с помощью сервиса можно заранее проверять планируемые изменения (напр. подключение сторонней аналитики).

Оптимизация метрик

Итак, мы узнали какие есть метрики и научились их анализировать. А значит пора переходить к их оптимизации.

Оптимизация изображений

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

Сжатие изображений

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

img_compress.png
Изображение до/после сжатия на 80%

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

  • Сжимать вручную через веб сервисы или специальные инструменты;

  • Сжимать во время сборки;

  • Сжимать динамически через сторонний сервис или бэкенд приложения.

Уменьшение размера изображения

Если изображение используется в блоке 400х200 - ему не нужно быть 1920х1080. Никакой экран не отобразит такое качество в таком блоке. Стоит использовать изображение не более 2х от блока. Такой запас полезен, например, для retina экранов.

- <img style="width:200px" src="/example_1920x1080.png"></img>
+ <img style="width:200px" src="/example_400x225.png"></img>

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

Смысл ленивой загрузки в том, чтобы отложить загрузку файлов до того момента, как они будут нужны – в случае изображений – до момента их видимости.

Для этого в первую очередь стоит использовать нативное браузерное loading="lazy". Атрибут даёт желаемый результат при быстром интернете. При медленном же все изображения встают в очередь сразу. Чтобы в дальнейшем, листая страницу, пользователь не встречал пустые пространства. Но при этом изображения встают вниз очереди потока, тем самым давая пользователю загрузить сперва только самые необходимые файлы, а уже потом загружать изображения.

<img style="width:200px" src="/example_400x225.png" loading="lazy"></img>

При этом важно, что этот атрибут не должен указываться для критических изображений (например крупного изображения из первой секции [LCP]).

Использование прогрессивных форматов

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

В среднем же оптимальный выбор формата помогает уменьшить вес изображений на >50%.

Динамический выбор изображения

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

Для показа разных изображений в зависимости от условий удобнее всего использовать тег <picture>. В нём можно указывать какое изображение показывать под какой размер и тип экрана.

<picture>
  <source srcset="/example_800x450.webp" media="(min-width: 1080px)" type="image/webp" />
  <source srcset="/example_800x450.png" media="(min-width: 1080px)" />
  <source srcset="/example_400x225.webp" type="image/webp" />
  <img src="/example_400x225.png" loading="lazy" />
</picture>

Приятным бонусом picture является то, что ему можно передать несколько форматов. Например webp как основной и png как запасной, на тот редкий случае когда webp не поддерживается или, по какой-то причине, отключён.

Динамический выбор изображения в CSS

Для изображений в CSS в вопросе размера экрана всё просто - сделать медиа и внутри определять background-image с нужный вариантом. Но в вопросе формата всё сложнее. Для таких ситуаций можно добавить блокирующий скрипт в начало страницы, который добавит нужные классы на всю страницу (.img-webp, .img-2x) и в зависимости от этих классов прописывать изображения в CSS.

.example-block {
  background-image: url("/example_400x225.png");
}

.webp .example-block {
  background-image: url("/example_400x225.webp");
}

Спрайт

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

image.png
Пример загрузки множества легковесных иконок

Лучшее решение в такой ситуации - объединить все иконки в один файл (спрайт). Это может быть как SVG, так и PNG изображение.

В случае SVG (svg sprite) создаётся единый svg файл, в котором каждая иконка выделяется в отдельный тэг <symbol />.

<svg xmlns="<http://www.w3.org/2000/svg>">
  <symbol viewBox="0 0 24 24" id="check">
    <path d="M20 6L9 17L4 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  </symbol>
  <symbol viewBox="0 0 24 24" id="close">
    <path d="M17 7L7 17M7 7L17 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  </symbol>
</svg>

И затем используется с помощью тэга use

<div>
  <svg width="20" height="20">
    <use href="/sprite.svg#check"></use>
  </svg>
</div>

Критические иконки (если вдруг такие есть) можно выделить в дополнительный спрайт и встроить его внутрь html страницы.

В случае PNG [или другого растрового формата] - создаются классы, обрезающие из большого изображения конкретную иконку (CSS sprite). Таким подходом часто пользуются карты.

.icon {
  background: url("/sprite.png");
  height: 20px;
  width: 20px;
}
.icon-check {
  background-position: 20px 0px;
}
.icon-close {
  background-position: 40px 0px;
}

Инлайн изображений

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

Оптимизация видео

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

  1. Изначально вместо видео отображается превью-изображение;

  2. При разгрузке потока или при наведении на превью начинает загружаться логика плеера;

  3. При клике на превью или кнопку просмотра видео - начинает загружаться и воспроизводиться само видео.

YouTube в некоторых разделах при наведении показывает GIF-превью видео, а при клике ведёт на полноценный плеер.

Сжатие статики

Важной оптимизацией является настройка сжатия. Особенно полезно сжатие для JS, HTML и CSS файлов. Сжатие позволяет уменьшить вес запросов примерно на 50-70% (больше или меньше зависит от степени сжатия, веса файлов и наполнения).

Cжимать файлы можно во время сборки, зафиксировав, brotli или gzip файлов. Лучше при этом выбирать именно brotli, так как он на 20% эффективнее чем gzip.

Чаще при этом сжатие доверяют CDN или серверу (в большинстве случаев, за это отвечает nginx). Но при неправильной настройке (например отсутствии кеширования) это может привести к задержке ответа сервера.

Оптимизация стилей

Оптимизация вставки стилей

Есть 3 способа вставки стилей на страницу:

  • inline, когда стили приписываются элементу через атрибут style;

  • embedded, когда стили вставляются в сам документ в тег <style />;

  • external, когда стили загружаются отдельным файлом.

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

Оптимизация применения стилей

При рендере страницы, изменении ДОМ элементов или изменении стейта элемента (hover, focus и т.п.) происходит перерасчёт стилей. В некоторых ситуациях такой перерасчёт может приводить к явным провисаниям сайта.

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

<link
  rel="stylesheet"
  href="mobile.css"
  media="screen and (max-width: 600px)"
/>

Оптимизация селекторов

Менее очевидная оптимизация. Обработка отдельных селекторов может быть тяжёлой операцией. Например если есть селектор body.test-a .block[data-test=a] - браузеру при изменении класса body нужно будет сопоставить все элементы .block на странице.

  • Уменьшать сложность селектора (например отталкиваться не от всего body, а от конкретной секции);

  • Уменьшать количество потенциальных элементов сопоставления (например использовать более специфический класс, а не повсеместный block);

  • Не использовать ненужные селекторы (например если на текущей странице вообще нет такого теста).

Подробнее про оптимизации и отладку селекторов можно прочитать на странице - https://developer.chrome.com/docs/devtools/performance/selector-stats?hl=en#analyze-stats.

Откладывание отрисовки

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

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

.map {
  content-visibility: auto;
  contain-intrinsic-size: 1000px;
}

Свойство contain-intrinsic-size позволяет зафиксировать высоту под элемент (похоже на height для img с loading="lazy")

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

Оптимизация шрифтов

Шрифты также часто являются слабым местом сайта, так как являются критическим и при этом достаточно тяжёлым ресурсом.

Оптимизация набора глифов

В первую очередь нужно провести ревизию шрифтов на странице. Точно ли нужны все шрифты, начертания, варианты и так далее. Оставив только самые нужные (или настроив вариативные шрифты) можно переходить к оптимизации их загрузки.

@font-face {
  font-family: "Inter";
  src: url("Inter-Regular-webfont.woff2") format("woff2");
  unicode-range: U+0025-00FF;
}

Сценарий загрузки

В плане загрузки шрифтов есть два основных сценария:

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

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

Какой из них выбрать зависит от ситуации, так как оба имеют как положительные, так и отрицательные эффекты.

В некоторых случаях также используется фоновая загрузка шрифтов и их использование только для последующих входов пользователя.

Предзагрузка

Ещё одно возможное улучшение - предзагрузка шрифтов. Таким способом шрифт займёт место в потоке раньше остальных ресурсов.

<link rel="preload" href="/Inter-regular.woff2" as="font" type="font/woff2" crossorigin>

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

Ускорение ответа сервера

Так как ресурсы грузятся поездом - одни из других - задержка ответа сервера имеет накопительный эффект. Например, если время задержки сервера 60мс, а изображение из первой секции грузится через JS - оно будет загружено на 120мс позже, чем при задержке в 20мс (40мс задержки на сам HTML, затем 40мс задержки на скрипт, затем 40мс задержки на изображение).

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

Для уменьшения задержки может помочь:

  • Упомянутое статическое сжатие вместо динамического;

  • Оптимизация логики сервера (напр. промежуточные обработчики или плохо оптимизированные обработчики реврайтов/редиректов);

  • Использование CDN. Пользователя обслуживает не сервер с другого конца планеты, а ближайшая точка распределённой сети;

  • Обновление протокола передачи данных (HTTP/2, HTTP/3).

image.png
Сравнение протоколов от Maria A. - https://www.cloudpanel.io/blog/http3-vs-http2/#:~:text=4. Connection Establishment Time

Кеширование

Лучшая оптимизация запросов - избегание запросов. И один из способов такой оптимизации - кеширование запросов.

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

В основном для кеширования используются:

  1. Заголовок Cache-Control. Он позволяет установить время, на которое браузер может закешировать ответ. Такой подход нужно использовать только в комплексе с хешем в имени файла. Иначе пользователь не сможет получить обновлённые ресурсы (из-за чего будут происходить неожиданные артефакты).

    image.png
    Пример заголовка Cache-Control
  2. Заголовки Etag и Last-Modified. Они позволяют браузеры отслеживать были ли изменения контента и, только если они были, загружать файл, иначе же браузер возьмёт данные из кеша. При таком подходе браузеру нужно отправить дополнительный запрос к серверу, что вызывает задержки. Поэтому в первую очередь стоит использовать Cache-Control, а для особенных случаев Etag или Last-Modified.

    image.png
    Пример заголовка Etag

Прочее

Оптимизация скачков

Ещё одно важное улучшение, влияющее на ощущение сайта - уменьшение скачков интерфейса (CLS). Они происходят, например, когда пользователь загрузил страницу [возможно даже начал ей пользоваться], а затем догрузились элементы из-за которых весь интерфейс сдвинулся. Это могут быть баннеры, изображения, динамические виджеты и так далее.

Основное решение для большинства случаев - заложить под блок наиболее точное пространство. Например под баннер над шапкой можно зафиксировать минимальную высоту как 60px для компьютера, и 120px для мобильной. Для изображений стоит использовать встроенные механизмы:

  • Задавать width и height и браузер сам определит соотношение сторон и заложит нужное пространство под текущий контейнер;

  • Задавать aspect-ratio через стили.

Избегание блокирующих элементов

Некоторые элементы страницы могут полностью останавливать рендеринг и её дальнейшую обработку. Такие элементы называют блокирующими. Так как пока они не загрузятся и/или выполнятся - будут заблокированы загрузка необходимых ресурсов и показ контента страницы в целом.

В качестве таких элементов стоит отметить:

  1. Встроенные стили и скрипты (вставленные внутрь тэгов <style> и <script>);

  2. Вставленные в <head /> элементы <link /> (например <link rel=stylesheet href="...">);

  3. Внешние тэги <script /> без атрибута defer или async.

Постскриптум

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

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

Комментарии (6)


  1. irahska
    18.02.2025 10:37

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


    1. Vordgi Автор
      18.02.2025 10:37

      Безусловно. Это просто один из факторов, который я постепенно собирал и наконец решил опубликовать.

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

      Но если скорость критично низкая - теряете и первичные факторы. А если максимально высокая - выигрываете.

      А в целом высокая производительность и максимальные web vitals - проблемы скорее развитых бизнесов, когда остальное уже отточено. Для стартапов закапываться в это не лучшая идея (но здесь много того, что можно сделать и за 5 секунд, поэтому "почему бы и да")


  1. monochromer
    18.02.2025 10:37

    Оптимизация набора глифов

    Какими инструментами пользуетесь для выполнения таких оптимизаций?


    1. Vordgi Автор
      18.02.2025 10:37

      Конкретно у нас с этим ничего особенного - Next.js и его next/font, в котором можно настраивать сабсеты - https://nextjs.org/docs/pages/building-your-application/optimizing/fonts#specifying-a-subset (по сути убираем только совсем уж лишнее).

      Это встроено в google fonts - можно добавить query-параметр (напр. &subset=cyrillic) и всё будет работать https://developers.google.com/fonts/docs/getting_started#specifying_script_subsets.

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

      Но возможно среди читающих найдётся кто-то с более богатым опытом.


      1. monochromer
        18.02.2025 10:37

        Насколько помню, `next/font` работает только со шрифтами из Google Fonts. Со своими локальными шрифтами он не умеет работать.

        Сам же для таких задач пользуюсь Python Fonttools.


        1. Vordgi Автор
          18.02.2025 10:37

          Да. Но в целом если цель те-же шрифты сохранить локально - можно повторить ту логику что делает google fonts - https://fonts.googleapis.com/css?family=Inter&subset=cyrillic, загрузить нужные сабсеты локально (https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZthiI2B.woff2) и настроить на локальные пути.

          Спасибо за вариант решения!