Разработка карточки товара для интернет-магазина
Карточка товара — главная конверсионная страница магазина. Здесь пользователь принимает решение о покупке, и каждый элемент интерфейса либо помогает ему, либо создаёт трение. Технически карточка сложнее, чем кажется: она собирает данные из нескольких источников, должна работать быстро при любой нагрузке и быть SEO-оптимизированной с первым байтом HTML.
Структура данных карточки
Одна страница карточки агрегирует данные из разных таблиц:
Product
├── Варианты (sku, цена, наличие)
├── Изображения (по вариантам и общие)
├── Атрибуты (specs, характеристики)
├── Описание (rich text)
├── Категория + Breadcrumb
├── Рейтинг (агрегированный) + последние отзывы
├── Похожие товары
├── «Часто покупают вместе»
└── Цена с историей скидок
Всё это нельзя грузить одним запросом без N+1 проблем. Типовая стратегия:
- SSR основного контента — название, главное изображение, цена, кнопка «Купить». Приходит с первым HTML, индексируется поисковиком.
- Lazy-load блоков — отзывы, похожие товары, «с этим покупают» — загружаются после DOMContentLoaded через отдельные API-запросы.
- Кеш карточки — полный HTML страницы кешируется на CDN с инвалидацией при изменении товара.
Выбор варианта
Если товар имеет варианты (цвет × размер), интерфейс выбора — ключевой элемент. Требования:
- Недоступные комбинации — визуально заблокированы (зачёркнуты или серые), не кликабельны
- При выборе варианта обновляются: изображение, цена, наличие, SKU в URL
- Если вариант закончился — показываем «Нет в наличии» + кнопку «Уведомить о поступлении»
type VariantSelector = {
attributes: { id: number; name: string; values: VariantValue[] }[];
selection: Record<number, string>;
onSelect: (attributeId: number, value: string) => void;
};
function isAvailable(selection: Record<number, string>, variants: Variant[]): boolean {
return variants.some(v =>
Object.entries(selection).every(([attrId, val]) =>
v.attributes[attrId] === val
) && v.inStock
);
}
URL обновляется через pushState при каждом выборе: /product/sneakers-air-max?color=black&size=42. Это позволяет поделиться ссылкой на конкретный вариант и работает с кнопкой «Назад».
Блок цены
Цена — не просто число. Типичный набор состояний:
- Обычная цена
- Цена со скидкой (зачёркнутая старая + новая)
- Диапазон цен для товаров с вариантами («от 2 990 руб.»)
- «Цена по запросу» для B2B-товаров
- «Войдите для просмотра цены» (оптовые клиенты)
История цены: небольшой график изменения цены за 30–90 дней — сигнал доверия. «Минимальная цена за 30 дней: 3 490 руб.» — аналог маркировки на WB и Ozon.
Обратный отсчёт до конца акции: если discount.ends_at задан, показываем таймер. Реализация на клиенте через setInterval, синхронизация с серверным временем при загрузке страницы.
Блок наличия и доставки
Пользователь хочет знать: когда он получит товар? Это важнее, чем сама кнопка «Купить».
- «Есть в наличии: 12 шт.» (или «Мало осталось: 2 шт.» при qty < 5)
- «Доставка завтра» — если оформить до 18:00 (вычисляется по текущему времени + графику работы склада)
- «Самовывоз сегодня» — список ближайших точек выдачи с наличием
Расчёт даты доставки — серверная логика: рабочие дни склада, регион пользователя, тип доставки. Передаётся как строка в API-ответе, не вычисляется на клиенте.
Блок описания
Rich text описание рендерится из HTML или markdown. Требования:
- Длинные описания сворачиваются до 5–6 строк с кнопкой «Читать далее» (CSS
overflow: hidden+ JS для toggle) - Таблицы характеристик — отдельный блок с группировкой атрибутов
- Вкладки: «Описание» / «Характеристики» / «Отзывы» / «Вопросы»
Вкладки реализуются через URL hash (#reviews) или search params (?tab=reviews). Это позволяет ссылаться напрямую на блок отзывов — важно для SEO.
Блок отзывов
Отзывы — конверсионный элемент и SEO-контент одновременно.
Структура:
- Агрегированный рейтинг (звёзды + распределение по баллам: гистограмма 1–5)
- Кнопка «Написать отзыв» (форма с рейтингом, текстом, загрузкой фото)
- Список отзывов с пагинацией (или lazy load)
- Фильтры: «Только с фото», «5 звёзд», «Последние»
Антиспам для отзывов: разрешаем только авторизованным, только тем, кто купил товар (проверка по orders). Модерация — очередь для менеджера.
Schema.org разметка: AggregateRating с ratingValue, reviewCount. Это влияет на звёздочки в результатах поиска (rich snippets).
CTA — кнопка покупки
Кнопка «Купить» / «В корзину» — главный элемент страницы. Паттерны:
- «В корзину»: добавляет в корзину, пользователь продолжает просмотр. Подходит для магазинов с частыми множественными покупками.
- «Купить сейчас»: добавляет и редиректит на чекаут. Укорачивает путь для целевого покупателя.
- Sticky CTA: при скролле вниз появляется фиксированная панель с ценой и кнопкой. Удерживает конверсионный элемент в зоне видимости.
При добавлении в корзину — feedback: анимация иконки корзины, мини-попап или drawer с подтверждением. Пользователь должен чувствовать, что действие выполнено.
SEO-разметка
<!-- Open Graph для шаринга в соцсетях -->
<meta property="og:title" content="Ноутбук Apple MacBook Air 13 M3 — Название магазина">
<meta property="og:image" content="https://cdn.../product-main.jpg">
<meta property="og:type" content="product">
<!-- Schema.org Product -->
<script type="application/ld+json">
{
"@type": "Product",
"name": "Apple MacBook Air 13",
"image": ["https://cdn.../img1.jpg"],
"description": "...",
"brand": { "@type": "Brand", "name": "Apple" },
"offers": {
"@type": "Offer",
"price": "89990",
"priceCurrency": "RUB",
"availability": "https://schema.org/InStock",
"priceValidUntil": "2025-12-31"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "127"
}
}
</script>
Связанные товары
«Похожие товары» и «С этим покупают» — разные алгоритмы:
- Похожие: товары той же категории с похожими атрибутами (та же ценовая группа, тот же бренд или атрибуты)
-
Часто покупают вместе: коллаборативная фильтрация — анализ пар товаров в одном заказе. Самый простой вариант:
SELECT product_id, COUNT(*) FROM order_items WHERE order_id IN (SELECT order_id FROM order_items WHERE product_id = :id) GROUP BY product_id ORDER BY COUNT(*) DESC LIMIT 5
Производительность
-
LCP (главное изображение):
<img loading="eager" fetchpriority="high">, preload в<head>, WebP с fallback -
CLS (сдвиг контента): зарезервировать место для изображения через
aspect-ratioили явныеwidth/height -
Кеш: HTML карточки кешируется на CDN (Cloudflare) с
s-maxage=300, инвалидация через Cloudflare API при изменении товара
Сроки
- Базовая карточка (фото, цена, варианты, кнопка, характеристики): 2–3 недели
- Полноценная карточка (отзывы, похожие товары, sticky CTA, история цены, Schema.org, производительность): 4–6 недель







