Frontend Engineer · Фев 2024 — Июль 2025
ОхотАктив · Производительность
Next.js · React · TypeScript · react-virtualized · Core Web Vitals · webpack-bundle-analyzer
Что это и с чем я пришёл
Крупный интернет-магазин товаров для охоты и рыбалки — до 10 млн просмотров в месяц, каталог до 400 тысяч товаров. Я пришёл незадолго до релиза React-версии сайта. Релизили в спешке: много кода писалось прямо в текучке, и переделать вёрстку до релиза глубоко было нельзя — сроки не позволяли. Так что стартовая точка была не «чистый лист», а живой продукт с уже заложенными проблемами производительности, которые нужно было чинить, не останавливая витрину.
Проблема
Lighthouse на каталоге показывал 40–50 из 100. Причина была в самом каталоге: десятки фильтров, у каждого — сотни значений, рядом карточки товаров, и всё это в одной адаптивной вёрстке сразу на десктоп и мобилку. На выходе получался очень тяжёлый HTML: браузеру приходилось строить и держать огромное дерево целиком. На ПК это лагало, на мобилке было совсем плохо — особенно когда пользователь открывал фильтры и в дерево добавлялись ещё сотни узлов.
Виртуализация фильтров
Первое, что я сделал — виртуализировал фильтры на клиенте готовой библиотекой (react-virtualized). Смысл простой: список на сотни значений не нужно рендерить целиком, если пользователь видит десяток строк за раз. Рендерится только видимая часть, остальное подставляется при скролле. Это разом сняло основную нагрузку с самого тяжёлого места страницы — дерево фильтров перестало раздувать DOM, и открытие фильтров, которое сильнее всего убивало мобилку, перестало лагать.
Отдача по User-Agent — и почему это не клоакинг
Дальше я развёл, что уходит поисковому боту и что — живому пользователю, по User-Agent. Боту нужно ровно то, что он индексирует: мета, семантика, все фильтры, весь значимый контент. Ему не нужен неинтерактивный обвес — обёртки, модалки и их стили с JS: он с ними всё равно не взаимодействует, а весят они прилично. Поэтому боту я этот обвес не отправлял, а пользователю уходила полная интерактивная версия.
Важная оговорка: это не streaming SSR и не клоакинг. Контент и семантика у бота и у пользователя одни и те же — бот видит ровно то же, что человек. Отличается только неинтерактивный обвес, которым боту нечего делать. Так поисковик получал чистую лёгкую страницу для индексации, а пользователь — весь интерактив, и никто из них не тянул лишнего.
Чистка первого рендера
Отдельно я вычистил первый экран. Всё, что не влияет ни на SEO, ни на первую отрисовку, ушло в динамические импорты и подгружалось позже: лишний CSS, JS и куски HTML не должны блокировать first paint. Задача была довести первый рендер до минимума — чтобы браузер как можно раньше показал пользователю значимое, а тяжёлое подъезжало уже после.
Почему фронт ускорил и сам сервер
Тут сыграла инфраструктура. Бэкенд был закэширован и быстр, а фронт и бэк стояли в одной сети на сервере — данные между ними летели практически мгновенно. Из-за этого узким местом ответа был именно фронт: пока Next.js собирал и отдавал тяжёлую страницу, уходило время. Как только страница похудела, время ответа Next.js резко упало — оптимизация фронта напрямую ускорила отдачу сервера.
Результат
Lighthouse на каталоге поднялся с 40–50 до 100/100/100 — Performance, Accessibility, Best Practices. FCP упал на 73% (1.1s → 0.3s), LCP — на 63% (3s → 1.1s). Время ответа Next.js было ~1–1.5s с пиками до 3s, стало 0.25–0.30s почти на всех страницах (на тестовом стенде добивал до 0.16s). SEO-специалист отдельно отметил, что боты стали краулить сайт кратно быстрее — читаемость страниц для поисковика выросла вместе со скоростью.
Что это дало бизнесу
Ускорение потянуло за собой позиции в выдаче. Рейтинг сайта заметно вырос — почти догнали единственного конкурента, от которого раньше сильно отставали. Часть каталогов и страниц поднялась на много позиций и вышла на первую страницу поиска, некоторые заняли первое-второе место. В выдаче мы перебили многие страницы маркетплейсов — при их несопоставимо большем продвижении, бюджетах и узнаваемости.