Однажды мы заметили, что поисковая выдача в мобильной версии Циан замедлилась: плохо работала на стороне браузера, грузилось много ресурсов, приложение долго открывалось. Естественно, это не радовало пользователей и сказывалось на метриках. В мае 2021-го Google анонсировал изменения: с августа он станет учитывать метрики Core Web Vitals в ранжировании поисковой выдачи. Мы стали искать, в чём может быть наша проблема. В этой статье расскажем, где же проблема крылась, и как мы её решили.

Core Web Vitals (CWV)

Google предложил метрики для определения качества страниц: FID, LCP и CLS. Дословно Core Web Vitals — это основные интернет-показатели. Каждый из этих показателей представляет собой отчётливый аспект пользовательского опыта. На текущий момент основными показателями являются загрузка, интерактивность, визуальная стабильность.

FID — First Input Delay

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

LCP — Largest Contentful Paint

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

CLS — Cumulative Layout Shift

Совокупное смещение макета. Эта метрика чуть хитрее, поскольку состоит из произведения двух параметров: доли неожиданно смещаемых элементов относительно видимой области и расстояния, на которое элементы сместились. В настоящее время учитываются смещения с 1-й по 5-ю секунду после начала отрисовки. То есть если <div /> схлопнуть после начала 6-й секунды, то внезапно CLS примерно с 0,2499 упадёт до 0, если другие элементы останутся на своих местах. (Подробнее показатель описан в этой статье.)

Самодиагностика

Промышленное решение

У нас хороший CLS, прекрасный FID, и подводил только LCP. Мы знаем это благодаря тому, что у нас есть единый отлаженный механизм мониторинга наших сервисов через Grafana. Но пришлось его научить показывать CWV-метрики. С помощью пакета web-vitals мы стали накапливать гуглометрики с реальных пользователей. Затем настроили удобные графики и начали отслеживать динамику. 

Работать с web-vitals оказалось комфортно. Для него не обязательно использовать выделенные хранилища и Grafana (у нас всё это просто уже было настроено и стандартизировано). Метрики можно писать прямо в Google Analytics, там тоже есть графики, разрезы и динамика. Рекомендуем, тем более есть инструкция.

Пока данные накапливались, мы прогнали ключевые страницы через аналитические сервисы. Нам понравились Google Search Console, WebPageTest и Lighthouse. Мы в основном использовали последний, но хороших слов заслуживают все.

Коротко об этом под катом

Google Search Console 

Это возможность посмотреть на сайт глазами Google. Данные собираются непосредственно с пользователей Google Chrome. Google Search Console разделяет метрики мобильной и десктопной версий сайта и позволяет просматривать состояние по каждому из основных интернет-показателей постранично.

Для доступа к сервису необходимо подтверждение владения доменом, поэтому посмотреть, как там у других, или оценить локальную версию (до прода) не получится.

WebPageTest

Бесплатный мощный инструмент для анализа производительности веб-страниц. Его основные преимущества:

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

  2. Внутри используется Lighthouse с открытым кодом для улучшения качества страниц.

  3. Можно делать тесты с разных локаций.

  4. Мультибраузерность.

  5. Бесплатность.

  6. А ещё можно развернуть свой инстанс.

Lighthouse

Инструмент для аудита конкретных страниц:

  1. Анализирует данные из navigator.performance;

  2. Анализирует загрузку ресурсов и ответы API;

  3. Внутри себя имеет набор разных алгоритмов, которые вычисляют метрики вроде TTI;

  4. Формирует человекопонятный отчёт с рекомендациями к работе.

Ситуация нас не удивила:

  • CLS — прекрасен, у нас очень чёткая сетка и для мобильной, и для адаптивной версии. Впрочем, мы тоже нашли, что улучшить, но об этом дальше.

  • FID — от «хорошо» до «терпимо», на самом деле этот параметр не так просто сломать.

  • LCP — будем чинить.

Минутка занимательной арифметики

С 29 марта по 4 апреля 2 471 156 загрузок нашей мобильной поисковой выдачи и карточек объявления заняли как минимум на 1 секунду больше, чем должны были по Core Web Vitals. А это уже 28 суток. Целый февраль!

Итак, мы подтвердили очевидное с цифрами в руках. Теперь пора решать проблему.

Берём Lighthouse, настраиваем тесты, запускаем.

Результат первого прогона
Результат первого прогона

Внезапно наибольшей проблемой оказывается JavaScript.

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

Проблемы и решения вне кода

CDN

У нас есть CDN — это географически распределённые хранилища. Когда вы открываете сайт Циан, вам в браузер загружается не только базовый HTML, но ещё скрипты и картинки. 

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

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

GTM — Google Tag Manager

GTM — это удобный инструмент для подключения сторонних аналитических скриптов. Идея прекрасна: разработчик один раз внедряет GTM на страницу, и всё. Дальше его работа заканчивается, добавить или удалить внешний скрипт может любой пользователь, у которого есть права. 

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

Нам понадобилось время на то, чтобы аналитики вычистили неиспользуемое и дубли. В дальнейшем мы договорились о процедуре подключения новых инструментов через ревью отдела веб-разработки. Также задумались о том, чтобы сделать A/B-подключение, если скриптов понадобится много. Это поможет сделать так, чтобы на часть пользователей приходилась только часть скриптов.

Проблемы и решения в коде

Наш сайт — полностью динамический. Любая страница собирается React’ом от и до. При этом большая часть страниц отдаётся в основном как HTML. Это старая добрая магия SSR — Server Side Rendering.

В чём плюсы:

  • поисковые роботы получают привычное им чтиво и прекрасно всё индексируют;

  • нагрузка ложится приличной частью на наши сервера, а не на устройство пользователя;

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

Всё это прекрасно работало. Кроме последнего пункта. 

Overсборка

Мы когда-то заморочились со сборками и всё прекрасно отладили. Сборка была компактной, правильно «побитой», лишнего особо ничего не улетало. 

А когда взяли webpack-bundle-analyzer, проанализировали текущую нашу сборку и удивились. В сборке оказалось много неиспользуемого. 

Мы решили начать с нуля, применив практики по оптимизации и ускорению:

1. Грамотно поделили сервисы на чанки;

2. Настроили хороший tree-shaking с помощью Webpack 5;

3. Использовали Preact в режиме compat;

4. Применили content-visibility для изображений;

5. Заинлайнили критический CSS;

6. Убрали неиспользуемые зависимости.

Подробности расскажем ниже.

Артефакты прошлых лет

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

  1. common.css — базовые стили, reset.css и пр.;

  2. grid.css — старая сетка сайта;

  3. sentry.js — инструмент для сбора всех клиентских ошибок. Со временем нам стало очевидно, что мы не хотим собирать всё подряд, и на смену этой библиотеке пришла наша, более легковесная и простая, но мы не убрали эту.

Что мы сделали:

  1. grid.css унесли в единственные её использующие 2 старых сервиса;

  2. common.css перенесли с CDN прямо в код HTML;

  3. sentry.js просто удалили.

Inline CSS

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

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

Время менять шапки

В Циан мы используем микросервисную архитектуру. Обычно в формировании страницы со стороны фронтенда участвует 3 сервиса: шапка, футер и микросервис конкретной страницы. Это сильно упрощает разработку и ускоряет деплой.

Самым медленным микросервисом оказалась шапка. Проблема была на стороне браузера: много стилей, много JS, крайне неоптимальное чанкование, проблемы с зависимостями и т. д. 

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

Причины тотального рефакторинга таковы:

  • архитектура устарела, новые функции требовали слишком много бойлерплейта для реализации;

  • текущая архитектура не предполагала ленивой загрузки функции;

  • компонентный набор устарел;

  • не было возможности кластеризации в части SSR.

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

Основной идеей стала загрузка по требованию. Шапка реализована не микроприложением, а куском HTML с теми функциями, которые нужны здесь и сейчас. Более того, большинство действий, которые можно выполнить с шапки, дозагружаются сразу после клика. Например, можно авторизоваться, кликнув по «Войти». В норме это незаметно, но мы предусмотрели и обработку ситуаций, когда нам не удаётся оперативно дозагрузить (обычно предупреждалка и/или повторная попытка подгрузки).

Теперь шапку необходимо было оттестировать и замерить. После внутренних тестов мы выложили новую шапку в A/B-режиме. Мы внимательно смотрели за логами и ловили обращения в службу поддержки. 

У нас изменилась структура деплоя, потому что микросервис шапки стал масштабируемым, и проблемы производительности SSR стало возможно решать «грубой силой», просто добавляя мощности.

И вскоре на новую шапку переехала и адаптивная (десктопная) версия. 

Content-visibility

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

Мы начали использовать CSS-свойство content-visibility. Браузеры умные: они начинают загружать изображения заранее, при «приближении» к ним, а не только когда уже виден серый прямоугольник. Мы использовали это свойство только и именно для картинок, что снизило время браузера на рендеринг страницы чуть ли не вполовину. Обычно пользователи приходят на выдачу с заранее накрученными параметрами: цена, комиссии и прочее их уже устраивают. И смотрят они именно на картинки.

Попутные находки

Мы используем React в разработке, а для прода — Preact в режиме Compact. Поскольку Preact на 99 % совместим, то всё взлетело с первого раза и дало как увеличение скорости рендера, так и уменьшение бандлов.

Иконки были сделаны универсально, как SVG. Неважно, являлись ли они растром PNG или вектором. Добрый webpack, видя PNG’шку в SVG’шке, преобразует файл картинки в Base64 и подключает его в бандл. Пришлось это почистить. 

Подводим итоги

Что у нас получилось со страницей поисковой выдачи на мобилке:

Было

Стало

Размер JS-бандла

500 kB gzipped

100 kB gzipped

FCP

4.3 s

2.8 s 

LCP

5.0 s

3.8 s

CLS

0.002

0.002

TTI

9.1 s

5.7 s

TBT

570 ms

490 ms

А вот наши итоги в целом:

  • Нам удалось войти в зелёные зоны по всем метрикам. 

  • Мы получили улучшенный, расширяемый и масштабируемый код.

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

  • Мы поняли необходимость и методы ранней диагностики проблем, в том числе с использованием метрик CWV.

  • Мы сделали наших пользователей чуть-чуть счастливее.

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