# Быстрая загрузка и кеширование товаров на витринах

> Переделал загрузку и кеширование товаров в JavaScript-библиотеке витрин inSales. Запросов за данными товаров стало примерно на 70% меньше, тяжёлые страницы открываются за 2–3 секунды вместо 5+.

- Адрес: https://anu3ev.com/ru/work/storefront-product-loading/
- Период: Декабрь 2024 – май 2025
- Где: inSales
- Роль: Мейнтейнер библиотеки: AJAX-компоненты, клиентский кеш, выкатка и документация
- Навыки: JavaScript, IndexedDB, Web Performance, AJAX, Web Caching, Debugging, API Integration, Technical Documentation, Cross-browser Compatibility, Liquid
- Результаты:
  - ≈70%: меньше запросов за данными товаров — по сравнению числа запросов до и после изменений
  - 2–3 с: загрузка тяжёлых товарных страниц, которые раньше грузились больше 5 с, в характерных замерах
  - 10+: стандартных шаблонов проверено, а также реальные страницы клиентов

## Задача

Витрины inSales работают на CommonJS — общей JavaScript-библиотеке, которая загружает данные товаров, пересчитывает цены и обслуживает корзину. Владелец магазина может поставить на страницу несколько товарных подборок, и на главной набирается 60–200 товаров. Liquid генерировал эти блоки ещё до отдачи страницы, а потом библиотека в браузере снова запрашивала по ним данные.

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

## Моя роль

Я сопровождал библиотеку и отвечал за эту работу на фронтенде: AJAX-компоненты, правила кеша, обработку ошибок, сценарии QA, план выкатки и документацию для разработчиков. Облегчённые ответы API делали бэкенд-разработчики: я описал, какие данные нужны превью, и встроил результат в библиотеку. Изменения проверял на более чем 10 стандартных шаблонах и на реальных страницах клиентов.

## Что я сделал

### AJAX-компоненты для товаров и фильтров

Товарные блоки больше не нужно было целиком генерировать в Liquid заранее. Я написал `ui-ajax-products` для списков товаров из категории и слайдеров, `ui-ajax-product` для загрузки одного товара по ID (например, в модальном окне выбора варианта) и AJAX-загрузку фильтров каталога. Разметку карточки разработчик магазина задаёт шаблонами внутри виджета. После отрисовки компоненты отправляют события вроде `init-products:ui-ajax-products`, и слайдер запускается только тогда, когда в нём уже есть товары. Цены, варианты и добавление в корзину идут через тот же товарный компонент, что и на остальной витрине, поэтому AJAX-карточки ведут себя как обычные.

![Страница ui-ajax-products в документации inSales для разработчиков](https://anu3ev.com/images/work/storefront-product-loading/ui-ajax-products-docs.webp)
*Публичная документация к `ui-ajax-products`, которую я написал.*

### Проверка по дате изменения вместо таймера

По таймеру не понять, изменился товар или нет. Я передал дату изменения каждого товара через Liquid в `data-product-updated-at` и стал хранить её вместе с записью в IndexedDB в поле `originalUpdatedAt`. При следующей загрузке страницы библиотека берёт из кеша товары с совпадающей датой и запрашивает только новые и изменившиеся. Товары, которые лежат в корзине, но не выведены на странице, берутся из сохранённых данных, а если записи нет — из `products_with_variants.json`. Весь видимый список при этом не перезагружается. Кеш живёт в браузере, сервер отдаёт только даты изменения.

### Лёгкие превью без потери вариантов

В крупных магазинах встречаются товары с 500–1000 вариантами, и список из 20 таких товаров мог грузиться 20–30 секунд или падать по таймауту. Превью в слайдере все варианты не нужны, поэтому я перевёл превью на облегчённый ответ (`without_many_variants`), а полные данные оставил там, где покупатель выбирает вариант. Из-за этого появился конфликт в кеше: один товар может одновременно быть в превью, в полном выборе вариантов, в модальном окне и в корзине. Если облегчённый ответ перезапишет полную запись, следующий виджет останется без вариантов. Если брать кеш без проверки, модальному окну не хватит данных. Я добавил правила полноты: каждое место отображения знает, какие данные ему нужны, а недостающие догружаются по требованию.

### Ошибки, которые проявлялись только в живых магазинах

На мобильном Safari карточка товара иногда не инициализировалась после «Загрузить ещё» в каталоге. Сначала это выглядело как зависание хранилища в самой карточке. Пошаговое воспроизведение показало, что каждый подгруженный товар запускал свою инициализацию и заново проходил по хранилищу. Я сделал пакетную инициализацию подгруженных товаров. В другом случае один товарный запрос с ответом 555 ронял инициализацию всех товаров, даже успешно загруженных. Я поправил обработку ошибок, и такие товары теперь инициализируются. Ещё я продлил показ прелоадера до конца отрисовки, подготовил изображения под нужные размеры и обсуждал с партнёрами заглушки для картинок, чтобы страница меньше прыгала при загрузке.

### Выкатка в два этапа

У магазинов разные поколения шаблонов и свои доработки, поэтому включить новый кеш везде сразу было нельзя. Сначала вышла новая версия библиотеки, которая для разметки без даты изменения сохраняла старую проверку по времени. Потом `data-product-updated-at` добавили в виджеты. В сценариях QA я описал переходы между страницами, изменение цены в админке, повторное открытие страницы после старого TTL, смешение облегчённых и полных данных, товары только в корзине и товары сразу в нескольких виджетах.

## Результат

- Запросов за данными товаров стало примерно на 70% меньше: так показало сравнение числа запросов до и после изменений.
- В характерных замерах тяжёлые товарные страницы стали загружаться за 2–3 секунды вместо больше чем 5. Результат зависит от числа виджетов, устройства, сети и состояния кеша.
- Изменения проверил на 10+ стандартных шаблонах и реальных страницах клиентов, в том числе со 60–200 товарами.
- Старые шаблоны продолжили работать во время выкатки, а разработчики магазинов получили документацию к новым компонентам.

Про саму библиотеку, её сборку и уменьшение бандла — [в отдельном кейсе о CommonJS](https://anu3ev.com/ru/work/commonjs-storefront-library/).
