Оптимизация страницы корзины для снижения брошенных корзин
По данным Baymard Institute, средний показатель брошенных корзин — 70.19%. Половина этих потерь связана с техническими и UX-проблемами, которые решаются на уровне разработки. Оставшаяся половина — с намерением (пользователь просто сравнивал цены), и здесь помогает email-ретаргетинг.
Основные причины брошенных корзин (по данным Baymard)
| Причина | % пользователей |
|---|---|
| Непредвиденные расходы (доставка, налоги) | 48% |
| Обязательная регистрация | 26% |
| Медленная загрузка / технические ошибки | 17% |
| Недоверие при вводе данных карты | 19% |
| Сложный процесс оформления | 18% |
Первый пункт — самый важный. Скрытые стоимости доставки, которые появляются только на checkout, — главный убийца конверсии.
Показывать стоимость доставки в корзине
Доставка должна рассчитываться прямо в корзине, до перехода на checkout. Виджет «Рассчитать доставку» с полем города/индекса:
const ShippingCalculator: React.FC<{ cartTotal: number }> = ({ cartTotal }) => {
const [zip, setZip] = useState('');
const [rates, setRates] = useState<ShippingRate[]>([]);
const calculate = async () => {
const result = await api.post('/shipping/rates', {
zip,
items: cartItems,
total: cartTotal,
});
setRates(result.data);
};
return (
<div className="shipping-calculator">
<input
placeholder="Введите индекс или город"
value={zip}
onChange={e => setZip(e.target.value)}
onBlur={calculate}
/>
{rates.map(rate => (
<ShippingOption key={rate.id} rate={rate} />
))}
{cartTotal >= FREE_SHIPPING_THRESHOLD && (
<FreeShippingBadge />
)}
</div>
);
};
Бесплатная доставка при сумме X — мощный стимул докупить. Прогресс-бар до порога:
const threshold = 5000; // рублей
const remaining = threshold - cartTotal;
<div className="free-shipping-progress">
<progress value={cartTotal} max={threshold} />
{remaining > 0
? <span>До бесплатной доставки осталось {remaining} ₽</span>
: <span>Бесплатная доставка!</span>
}
</div>
Такой прогресс-бар увеличивает средний чек на 10–30% у пользователей, которые изначально планировали купить меньше порога.
Сохранение корзины
Корзина должна сохраняться между сессиями. Для авторизованных — в базе данных, для гостей — в localStorage с синхронизацией при логине.
// Модель Cart (для авторизованных)
class CartItem extends Model {
protected $fillable = ['cart_id', 'product_id', 'variant_id', 'quantity'];
}
// При логине — merge гостевой корзины с серверной
public function mergeGuestCart(array $guestItems): void {
foreach ($guestItems as $item) {
$this->items()->updateOrCreate(
['product_id' => $item['product_id'], 'variant_id' => $item['variant_id']],
['quantity' => DB::raw("quantity + {$item['quantity']}")],
);
}
}
Abandoned cart emails
Письмо через 1 час после того, как пользователь ушёл с корзиной — один из самых высококонверсионных email-триггеров (recovery rate 5–15%).
Триггер: при добавлении в корзину создаём запись с abandoned_at = NULL. Если пользователь не завершил заказ в течение 60 минут и последняя активность была > 60 минут назад — помечаем корзину как брошенную.
// Scheduled Job (каждые 15 минут)
Cart::query()
->where('updated_at', '<', now()->subMinutes(60))
->whereNull('order_id') // незавершённый заказ
->whereNull('abandoned_email_sent_at')
->with('user', 'items.product')
->chunk(100, function ($carts) {
foreach ($carts as $cart) {
SendAbandonedCartEmail::dispatch($cart);
$cart->update(['abandoned_email_sent_at' => now()]);
}
});
Письмо содержит:
- Список товаров с фото и ценами
- Кнопку «Вернуться к заказу» (ссылка с UTM-меткой)
- Социальное доказательство (отзывы на товары из корзины)
Серия писем: через 1 час, через 24 часа, через 72 часа. Третье письмо может содержать скидку — но только если первые два не сработали.
Upsell и cross-sell в корзине
Рекомендации должны быть релевантными, а не случайными. Алгоритмы:
«Часто покупают вместе» — based on co-occurrence в исторических заказах:
SELECT
oi2.product_id,
COUNT(*) AS together_count
FROM order_items oi1
JOIN order_items oi2 ON oi1.order_id = oi2.order_id AND oi1.product_id != oi2.product_id
WHERE oi1.product_id = $current_product_id
GROUP BY oi2.product_id
ORDER BY together_count DESC
LIMIT 5;
«Улучши комплект» — если в корзине бюджетный вариант, предлагаем premium с разницей в цене. Конверсия в апгрейд — 8–15%.
Минимизация шагов до checkout
Кнопка «Оформить заказ» должна быть видна без скролла. На мобильных — фиксированная панель внизу экрана:
@media (max-width: 768px) {
.cart-checkout-btn {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
padding: 16px;
background: white;
box-shadow: 0 -2px 8px rgba(0,0,0,0.1);
}
}
Индикаторы наличия товара
Если товар заканчивается, показываем это в корзине:
{item.stock_count <= 5 && (
<span className="low-stock-warning">
Осталось {item.stock_count} шт.
</span>
)}
Это создаёт мягкое давление, не выглядя как манипуляция, — просто полезная информация.
Технические оптимизации
Optimistic UI при изменении количества. Пользователь кликает «+» — количество меняется мгновенно в UI, запрос к API идёт в фоне. Если запрос падает — откатываем:
const updateQuantity = async (itemId: number, newQty: number) => {
const prevQty = item.quantity;
setItems(prev => prev.map(i => i.id === itemId ? {...i, quantity: newQty} : i));
try {
await api.patch(`/cart/items/${itemId}`, { quantity: newQty });
} catch {
setItems(prev => prev.map(i => i.id === itemId ? {...i, quantity: prevQty} : i));
toast.error('Не удалось обновить количество');
}
};
Пересчёт итогов в реальном времени без перезагрузки страницы — базовое требование, но важно чтобы он был мгновенным.
Сроки
| Задача | Время |
|---|---|
| Калькулятор доставки + прогресс-бар | 1 день |
| Сохранение корзины для гостей | 1 день |
| Abandoned cart emails | 1–2 дня |
| Рекомендации (cross-sell) | 1–2 дня |
Итого: 3–5 рабочих дней.







