[{"data":1,"prerenderedAt":260},["ShallowReactive",2],{"project-ru-storefront-product-loading":3,"recommendation-mention-sources":198},{"id":4,"title":5,"body":6,"category":158,"context":159,"cover":160,"coverAlt":161,"demo":162,"description":163,"extension":164,"featured":165,"locale":166,"meta":167,"navigation":168,"order":169,"path":170,"published":168,"repository":162,"results":171,"role":181,"seo":182,"slug":183,"stack":184,"stem":195,"year":196,"__hash__":197},"projects\u002Fru\u002Fprojects\u002Fstorefront-product-loading.md","Быстрая загрузка и кеширование товаров на витринах",{"type":7,"value":8,"toc":143},"minimark",[9,14,18,21,25,28,32,37,53,67,71,86,90,97,101,104,108,114,118,134],[10,11,13],"h2",{"id":12},"задача","Задача",[15,16,17],"p",{},"Витрины inSales работают на CommonJS — общей JavaScript-библиотеке, которая загружает данные товаров, пересчитывает цены и обслуживает корзину. Владелец магазина может поставить на страницу несколько товарных подборок, и на главной набирается 60–200 товаров. Liquid генерировал эти блоки ещё до отдачи страницы, а потом библиотека в браузере снова запрашивала по ним данные.",[15,19,20],{},"Кеш усугублял проблему. Обычно данные товара жили 120 секунд, на странице товара — 60. Когда таймер истекал, неизменившиеся товары загружались заново, а если товар менялся раньше, покупатель мог видеть старые данные. После добавления одного товара в корзину библиотека перепроверяла весь список товаров страницы. Один клик после истечения кеша мог вызвать запросы за товарами, которых покупатель даже не касался.",[10,22,24],{"id":23},"моя-роль","Моя роль",[15,26,27],{},"Я сопровождал библиотеку и отвечал за эту работу на фронтенде: AJAX-компоненты, правила кеша, обработку ошибок, сценарии QA, план выкатки и документацию для разработчиков. Облегчённые ответы API делали бэкенд-разработчики: я описал, какие данные нужны превью, и встроил результат в библиотеку. Изменения проверял на более чем 10 стандартных шаблонах и на реальных страницах клиентов.",[10,29,31],{"id":30},"что-я-сделал","Что я сделал",[33,34,36],"h3",{"id":35},"ajax-компоненты-для-товаров-и-фильтров","AJAX-компоненты для товаров и фильтров",[15,38,39,40,44,45,48,49,52],{},"Товарные блоки больше не нужно было целиком генерировать в Liquid заранее. Я написал ",[41,42,43],"code",{},"ui-ajax-products"," для списков товаров из категории и слайдеров, ",[41,46,47],{},"ui-ajax-product"," для загрузки одного товара по ID (например, в модальном окне выбора варианта) и AJAX-загрузку фильтров каталога. Разметку карточки разработчик магазина задаёт шаблонами внутри виджета. После отрисовки компоненты отправляют события вроде ",[41,50,51],{},"init-products:ui-ajax-products",", и слайдер запускается только тогда, когда в нём уже есть товары. Цены, варианты и добавление в корзину идут через тот же товарный компонент, что и на остальной витрине, поэтому AJAX-карточки ведут себя как обычные.",[15,54,55,60],{},[56,57],"img",{"alt":58,"src":59},"Страница ui-ajax-products в документации inSales для разработчиков","\u002Fimages\u002Fwork\u002Fstorefront-product-loading\u002Fui-ajax-products-docs.webp",[61,62,63,64,66],"em",{},"Публичная документация к ",[41,65,43],{},", которую я написал.",[33,68,70],{"id":69},"проверка-по-дате-изменения-вместо-таймера","Проверка по дате изменения вместо таймера",[15,72,73,74,77,78,81,82,85],{},"По таймеру не понять, изменился товар или нет. Я передал дату изменения каждого товара через Liquid в ",[41,75,76],{},"data-product-updated-at"," и стал хранить её вместе с записью в IndexedDB в поле ",[41,79,80],{},"originalUpdatedAt",". При следующей загрузке страницы библиотека берёт из кеша товары с совпадающей датой и запрашивает только новые и изменившиеся. Товары, которые лежат в корзине, но не выведены на странице, берутся из сохранённых данных, а если записи нет — из ",[41,83,84],{},"products_with_variants.json",". Весь видимый список при этом не перезагружается. Кеш живёт в браузере, сервер отдаёт только даты изменения.",[33,87,89],{"id":88},"лёгкие-превью-без-потери-вариантов","Лёгкие превью без потери вариантов",[15,91,92,93,96],{},"В крупных магазинах встречаются товары с 500–1000 вариантами, и список из 20 таких товаров мог грузиться 20–30 секунд или падать по таймауту. Превью в слайдере все варианты не нужны, поэтому я перевёл превью на облегчённый ответ (",[41,94,95],{},"without_many_variants","), а полные данные оставил там, где покупатель выбирает вариант. Из-за этого появился конфликт в кеше: один товар может одновременно быть в превью, в полном выборе вариантов, в модальном окне и в корзине. Если облегчённый ответ перезапишет полную запись, следующий виджет останется без вариантов. Если брать кеш без проверки, модальному окну не хватит данных. Я добавил правила полноты: каждое место отображения знает, какие данные ему нужны, а недостающие догружаются по требованию.",[33,98,100],{"id":99},"ошибки-которые-проявлялись-только-в-живых-магазинах","Ошибки, которые проявлялись только в живых магазинах",[15,102,103],{},"На мобильном Safari карточка товара иногда не инициализировалась после «Загрузить ещё» в каталоге. Сначала это выглядело как зависание хранилища в самой карточке. Пошаговое воспроизведение показало, что каждый подгруженный товар запускал свою инициализацию и заново проходил по хранилищу. Я сделал пакетную инициализацию подгруженных товаров. В другом случае один товарный запрос с ответом 555 ронял инициализацию всех товаров, даже успешно загруженных. Я поправил обработку ошибок, и такие товары теперь инициализируются. Ещё я продлил показ прелоадера до конца отрисовки, подготовил изображения под нужные размеры и обсуждал с партнёрами заглушки для картинок, чтобы страница меньше прыгала при загрузке.",[33,105,107],{"id":106},"выкатка-в-два-этапа","Выкатка в два этапа",[15,109,110,111,113],{},"У магазинов разные поколения шаблонов и свои доработки, поэтому включить новый кеш везде сразу было нельзя. Сначала вышла новая версия библиотеки, которая для разметки без даты изменения сохраняла старую проверку по времени. Потом ",[41,112,76],{}," добавили в виджеты. В сценариях QA я описал переходы между страницами, изменение цены в админке, повторное открытие страницы после старого TTL, смешение облегчённых и полных данных, товары только в корзине и товары сразу в нескольких виджетах.",[10,115,117],{"id":116},"результат","Результат",[119,120,121,125,128,131],"ul",{},[122,123,124],"li",{},"Запросов за данными товаров стало примерно на 70% меньше: так показало сравнение числа запросов до и после изменений.",[122,126,127],{},"В характерных замерах тяжёлые товарные страницы стали загружаться за 2–3 секунды вместо больше чем 5. Результат зависит от числа виджетов, устройства, сети и состояния кеша.",[122,129,130],{},"Изменения проверил на 10+ стандартных шаблонах и реальных страницах клиентов, в том числе со 60–200 товарами.",[122,132,133],{},"Старые шаблоны продолжили работать во время выкатки, а разработчики магазинов получили документацию к новым компонентам.",[15,135,136,137,142],{},"Про саму библиотеку, её сборку и уменьшение бандла — ",[138,139,141],"a",{"href":140},"\u002Fru\u002Fwork\u002Fcommonjs-storefront-library\u002F","в отдельном кейсе о CommonJS",".",{"title":144,"searchDepth":145,"depth":145,"links":146},"",2,[147,148,149,157],{"id":12,"depth":145,"text":13},{"id":23,"depth":145,"text":24},{"id":30,"depth":145,"text":31,"children":150},[151,153,154,155,156],{"id":35,"depth":152,"text":36},3,{"id":69,"depth":152,"text":70},{"id":88,"depth":152,"text":89},{"id":99,"depth":152,"text":100},{"id":106,"depth":152,"text":107},{"id":116,"depth":145,"text":117},"Платформа витрин","inSales","\u002Fimages\u002Fwork\u002Fstorefront-product-loading\u002Fcache-validation-flow.svg","Схема проверки кеша. Liquid передаёт дату изменения товара, библиотека сверяет её с IndexedDB и запрашивает только изменившиеся товары",null,"Переделал загрузку и кеширование товаров в JavaScript-библиотеке витрин inSales. Запросов за данными товаров стало примерно на 70% меньше, тяжёлые страницы открываются за 2–3 секунды вместо 5+.","md",false,"ru",{},true,6,"\u002Fru\u002Fprojects\u002Fstorefront-product-loading",[172,175,178],{"value":173,"label":174},"≈70%","меньше запросов за данными товаров — по сравнению числа запросов до и после изменений",{"value":176,"label":177},"2–3 с","загрузка тяжёлых товарных страниц, которые раньше грузились больше 5 с, в характерных замерах",{"value":179,"label":180},"10+","стандартных шаблонов проверено, а также реальные страницы клиентов","Мейнтейнер библиотеки: AJAX-компоненты, клиентский кеш, выкатка и документация",{"title":5,"description":163},"storefront-product-loading",[185,186,187,188,189,190,191,192,193,194],"JavaScript","IndexedDB","Web Performance","AJAX","Web Caching","Debugging","API Integration","Technical Documentation","Cross-browser Compatibility","Liquid","ru\u002Fprojects\u002Fstorefront-product-loading","Декабрь 2024 – май 2025","kLzs-EbCSWqeXJDXkkdrSvri2YbmQhXPuj1UXedmvsM",[199,210,215,230,240,245,250,255],{"stem":200,"name":201,"date":202,"featured":145,"published":168,"mentions":203},"recommendations\u002Fandrey-stikheev","Andrey Stikheev","2026-09-08",[204,207],{"project":205,"quote":206},"product-image-editor","Among his most notable projects was a sophisticated browser-based image editor built with TypeScript and Fabric.js, now used by thousands of merchants.",{"project":208,"quote":209},"abandoned-carts","He also developed the frontend for a paid abandoned-cart product and contributed to the production migration from Vue 2 to Vue 3.",{"stem":211,"name":212,"date":213,"featured":162,"published":168,"mentions":214},"recommendations\u002Fdaniil-churakov","Daniil Churakov","2026-08-27",[],{"stem":216,"name":217,"date":218,"featured":152,"published":168,"mentions":219},"recommendations\u002Fdmitry-sadovsky","Dmitry Sadovsky","2026-08-22",[220,223,225,228],{"project":221,"quote":222},"visual-storefront","For years he was the driving force behind our visual storefront editor — a large, complex application where merchants build their online stores.",{"project":205,"quote":224},"It became the tool merchants use to design product images right inside the admin panel — the kind of component companies usually buy rather than build.",{"project":226,"quote":227},"image-editor","What impressed me most was the infographic editor he built essentially from scratch: a full canvas-based graphic editor with artboards, shapes, text styling, templates, and undo\u002Fredo history, powered by an editor core he developed himself.",{"project":208,"quote":229},"Our abandoned carts functionality — with multi-channel customer messaging, communication history, and analytics — was largely his work, from UI down to the details of how requests are cancelled and states stay in sync.",{"stem":231,"name":232,"date":233,"featured":234,"published":168,"mentions":235},"recommendations\u002Fegor-fedkin","Egor Fedkin","2026-08-31",1,[236,238],{"project":205,"quote":237},"Today, the tool is actively used by around 3,000 users.",{"project":208,"quote":239},"It has already generated approximately twice the revenue needed to cover its development costs.",{"stem":241,"name":242,"date":243,"featured":162,"published":168,"mentions":244},"recommendations\u002Femmanuel-stephen","Emmanuel Stephen","2026-10-11",[],{"stem":246,"name":247,"date":248,"featured":162,"published":168,"mentions":249},"recommendations\u002Fevgeny-lonshakov","Евгений Лоншаков","2026-09-21",[],{"stem":251,"name":252,"date":253,"featured":162,"published":168,"mentions":254},"recommendations\u002Froman-gorbachev","Роман Горбачев","2026-09-17",[],{"stem":256,"name":257,"date":258,"featured":162,"published":168,"mentions":259},"recommendations\u002Fvladimir-bedarev","Bedarev Vladimir","2026-09-16",[],1793195987474]