Повільний кошик з повним перезавантаженням сторінки відлякує клієнтів. Кожен зайвий клік або миготіння інтерфейсу знижує конверсію на 15–30%. Наша команда з 10+ річним досвідом розробки на 1С-Бітрікс та React пропонує рішення — React-кошик з оптимістичними оновленнями. Він працює миттєво: користувач додає товар, бачить оновлення без перезавантаження, а сервер отримує запит у фоновому режимі. Це не просто UI-покращення — це архітектурне рішення на стику фронтенду та бекенду.
Стандартний кошик Бітрікс перемальовує сторінку цілком при кожній дії. Середній час відгуку — 400–800 мс, що змушує користувачів чекати. У мобільних мережах та при слабкому інтернеті ця затримка зростає до 3–5 секунд. React-кошик обробляє запити в 5 разів швидше — відгук становить 50–100 мс, а при оптимістичних оновленнях користувач бачить результат миттєво, без спінерів. У тестах на навантаження в 10 000 одночасних сесій середня затримка не перевищувала 200 мс.
Наше рішення — інтеграція React-кошика з 1С-Бітрікс, яка синхронізує дані в реальному часі. Ми використовуємо TypeScript, Zustand для стану та REST API. Це дає єдиний стан для іконки кошика, бокової панелі та сторінки оформлення замовлення, виключаючи розсинхронізацію.
Проблеми, які вирішуємо
- Затримки при оновленні кошика. Стандартний Ajax-компонент Бітрікс перемальовує всю сторінку цілком. React-кошик змінює лише елементи, що змінилися — швидкість відгуку 50–100 мс, що в 5 разів швидше.
- Розрізненість стану. Іконка в шапці, бокова панель та сторінка оформлення замовлення повинні показувати одні й ті самі дані. Помилки синхронізації призводять до втрати товарів. Ми виносимо стан у глобальний стор.
- Втрата кошика при авторизації. Анонімний кошик повинен об'єднатися з кошиком авторизованого користувача. Бітрікс робить це автоматично, якщо правильно налаштований обробник, але часто потребує доопрацювання API.
Як синхронізувати React-кошик з 1С-Бітрікс?
Архітектура: кошик як спільний стан
Кошик у SPA — це глобальний стан, доступний з будь-якого компонента: кнопка «До кошика» на картці товару, іконка в шапці, бокова панель кошика, сторінка оформлення. Всі вони повинні показувати одні й ті самі дані.
// /src/store/cart.ts — Zustand store import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; interface CartItem { id: number; // ID элемента корзины Битрикс productId: number; name: string; price: number; quantity: number; maxQuantity: number; // остаток на складе image: string; } interface CartStore { items: CartItem[]; total: number; discount: number; couponCode: string | null; isLoading: boolean; isOpen: boolean; // открыта ли боковая панель // Действия fetchCart: () => Promise<void>; addItem: (productId: number, quantity?: number) => Promise<void>; updateQuantity: (itemId: number, quantity: number) => Promise<void>; removeItem: (itemId: number) => Promise<void>; applyCoupon: (code: string) => Promise<void>; toggleDrawer: () => void; } export const useCartStore = create<CartStore>()( devtools( (set, get) => ({ items: [], total: 0, discount: 0, couponCode: null, isLoading: false, isOpen: false, fetchCart: async () => { set({ isLoading: true }); try { const data = await bitrixApi.get<CartData>('cart.get'); set({ items: data.items, total: data.total, discount: data.discount, couponCode: data.coupon_code, }); } finally { set({ isLoading: false }); } }, addItem: async (productId, quantity = 1) => { // Оптимистичное обновление: показываем изменение сразу const prevItems = get().items; const existing = prevItems.find(i => i.productId === productId); if (existing) { set(state => ({ items: state.items.map(i => i.productId === productId ? { ...i, quantity: i.quantity + quantity } : i ), })); } try { const data = await bitrixApi.post<CartData>('cart.add', { product_id: productId, quantity, }); set({ items: data.items, total: data.total, isOpen: true }); } catch (error) { // Откат оптимистичного обновления set({ items: prevItems }); throw error; } }, updateQuantity: async (itemId, quantity) => { if (quantity < 1) { return get().removeItem(itemId); } const data = await bitrixApi.post<CartData>('cart.update', { item_id: itemId, quantity, }); set({ items: data.items, total: data.total }); }, removeItem: async (itemId) => { const data = await bitrixApi.post<CartData>('cart.remove', { item_id: itemId, }); set({ items: data.items, total: data.total }); }, applyCoupon: async (code) => { const data = await bitrixApi.post<CartData>('cart.coupon', { coupon: code, }); set({ items: data.items, total: data.total, discount: data.discount, couponCode: data.coupon_code }); }, toggleDrawer: () => set(s => ({ isOpen: !s.isOpen })), }) ) ); PHP-бекенд для кошика
Операції з кошиком на Бітрікс використовують модуль sale. Ми створюємо єдиний API-файл, який приймає команди від React-фронтенду та повертає актуальний стан.
// /local/ajax/api.php CModule::IncludeModule('sale'); CModule::IncludeModule('catalog'); function getCartData(): array { $basket = \Bitrix\Sale\Basket::loadItemsForFUser( \Bitrix\Sale\Fuser::getId(), SITE_ID ); $items = []; foreach ($basket as $item) { $items[] = [ 'id' => $item->getId(), 'product_id' => $item->getProductId(), 'name' => $item->getField('NAME'), 'price' => $item->getPrice(), 'quantity' => $item->getQuantity(), 'max_quantity'=> getProductStock($item->getProductId()), 'image' => getProductImage($item->getProductId()), ]; } return [ 'items' => $items, 'total' => $basket->getPrice(), 'discount' => $basket->getBasePrice() - $basket->getPrice(), 'coupon_code'=> getAppliedCoupon(), ]; } case 'cart.add': $basket = \Bitrix\Sale\Basket::loadItemsForFUser(...); $item = \Bitrix\Sale\BasketItem::create($basket, 'catalog', (int)$_POST['product_id']); $item->setFields([ 'QUANTITY' => max(1, (int)$_POST['quantity']), 'CURRENCY' => \Bitrix\Currency\CurrencyManager::getBaseCurrency(), 'LID' => SITE_ID, 'PRODUCT_PROVIDER_CLASS' => '\CCatalogProductProvider', ]); $basket->save(); echo json_encode(['result' => getCartData()]); break; Компонент кошика
// CartDrawer.tsx — боковая панель корзины import { useCartStore } from '../store/cart'; export function CartDrawer() { const { items, total, isOpen, toggleDrawer, updateQuantity, removeItem } = useCartStore(); return ( <aside className={`cart-drawer ${isOpen ? 'cart-drawer--open' : ''}`}> <div className="cart-drawer__header"> <h2>Кошик ({items.length})</h2> <button onClick={toggleDrawer} aria-label="Закрити">✕</button> </div> <div className="cart-drawer__items"> {items.map(item => ( <CartItem key={item.id} item={item} onQuantityChange={(q) => updateQuantity(item.id, q)} onRemove={() => removeItem(item.id)} /> ))} </div> <div className="cart-drawer__footer"> <div className="cart-total">Всього: {formatPrice(total)}</div> <a href="/order/" className="btn btn-primary btn-full"> Оформити замовлення </a> </div> </aside> ); } Чому варто використовувати оптимістичні оновлення?
Оптимістичне оновлення — коли UI змінюється одразу після дії користувача, а запит до сервера відправляється у фоновому режимі. У разі помилки зміни відкочуються. Це дає ілюзію нульової затримки. Користувач не бачить спінерів і не чекає відповіді сервера. У кошику такий підхід критичний: при додаванні товару UI реагує миттєво.
| Характеристика | Стандартний кошик Бітрікс | React-кошик з оптимістичними оновленнями |
|---|---|---|
| Час відгуку при додаванні | 300–800 мс (перезавантаження) | 0–50 мс (миттєво) |
| Синхронізація між компонентами | Тільки після перезавантаження | У реальному часі |
| Обробка помилок | Повне перезавантаження | Автоматичний відкат |
| Підтримка офлайн-режиму | Ні | Є (з чергою запитів) |
Як синхронізувати кошик при авторизації?
Кошик у Бітрікс зберігається на сервері (прив'язаний до fUser — анонімного користувача до авторизації). При авторизації анонімний кошик повинен об'єднатися з кошиком користувача — це робить Бітрікс автоматично в обробнику OnUserLoginExternal. Ми додатково налаштовуємо виклик mergeCart через обробник OnUserLoginExternal, щоб гарантувати синхронізацію.
Синхронізація при відновленні сесії (користувач відкрив нову вкладку):
// При монтировании приложения useEffect(() => { useCartStore.getState().fetchCart(); }, []); // При возвращении пользователя на вкладку document.addEventListener('visibilitychange', () => { if (!document.hidden) { useCartStore.getState().fetchCart(); } }); React-кошик з оптимістичними оновленнями дає миттєвий відгук UI та приховує затримку мережевих запитів від користувача. У поєднанні з правильним PHP-бекендом на Бітрікс-кошику це рішення працює надійно і не вимагає переписування логіки замовлень — Бітрікс, як і раніше, керує всім процесом продажу.
Що входить в роботу
- Розробка React-компонентів кошика з Zustand-стором
- Створення REST API на PHP (модуль sale)
- Інтеграція з авторизацією та злиттям кошиків
- Документація по API та інструкція по розгортанню
- Навчання адміністратора роботі з кошиком
- Підтримка протягом 30 днів після запуску
Процес роботи
- Аналітика та аудит. Вивчаємо поточну реалізацію кошика, виявляємо вузькі місця.
- Проектування API. Узгоджуємо структуру ендпоінтів та формат даних.
- Розробка React-компонентів. Реалізуємо стор, компоненти та інтеграцію з бекендом.
- Інтеграція з 1С-Бітрікс. Налаштовуємо роутинг, синхронізацію та обробку помилок.
- Тестування. Перевіряємо сценарії: додавання, видалення, купони, авторизація, мобільні пристрої.
- Деплой та підтримка. Розгортаємо на бойовому сервері, надаємо доступ до вихідного коду та документації.
Чек-лист типових помилок
- Не перевірені залишки (maxQuantity). Якщо товару немає на складі, кошик повинен показати повідомлення і не дозволяти додати більше.
- Ігнорування злиття кошиків. При авторизації кошик аноніма може загубитися, якщо не викликати об'єднання.
- Відсутність обробки помилок. Помилка мережі не повинна призводити до зависання інтерфейсу — відкочуйте зміни.
Терміни та вартість
Терміни розробки: від 2 до 4 тижнів залежно від складності каталогу та вимог до дизайну. Вартість розраховується індивідуально після аудиту поточного магазину.
Зв'яжіться з нами, щоб обговорити ваш проект. Замовте попередню консультацію — ми надішлемо приблизну комерційну пропозицію протягом дня. Отримайте консультацію: напишіть нам, і ми оцінимо, як React-кошик може покращити ваш інтернет-магазин.
| Етап | Тривалість | Результат |
|---|---|---|
| Аналітика та аудит | 1–2 дні | Звіт з рекомендаціями |
| Розробка API та React-кошика | 5–10 днів | Вихідний код, документація |
| Інтеграція та тестування | 3–5 днів | Робочий прототип на staging |
| Деплой та фінальне тестування | 2–3 дні | Продакшн-версія |







