Product Card Development for E-Commerce
Product card is the main conversion page of the store. Here the user decides to buy, and each interface element either helps or creates friction. Technically card is more complex than it seems: aggregates data from multiple sources, must work fast at any load, and be SEO-optimized with first byte HTML.
Data Structure
One page aggregates data from different tables:
Product
├── Variants (sku, price, availability)
├── Images (per variant and shared)
├── Attributes (specs, characteristics)
├── Description (rich text)
├── Category + Breadcrumb
├── Rating (aggregated) + recent reviews
├── Similar products
├── "Often buy together"
└── Price with discount history
Cannot load all this in one query without N+1. Typical strategy:
- SSR main content — name, main image, price, "Buy" button. Comes with first HTML, indexed by search engines.
- Lazy-load blocks — reviews, similar products, "buy with this" — load after DOMContentLoaded via separate API requests.
- Cache card — full page HTML cached on CDN with invalidation on product change.
Variant Selection
If product has variants (color × size), selection interface — key element. Requirements:
- Unavailable combinations — visually blocked (strikethrough or gray), not clickable
- On variant select update: image, price, availability, SKU in URL
- If variant out of stock — show "Out of stock" + "Notify when available" button
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 updates via pushState on select: /product/sneakers-air-max?color=black&size=42. Share specific variant link works with back button.
Price Block
Price — not just number. Typical states:
- Regular price
- Price with discount (strikethrough old + new)
- Price range for variants ("from 2,990 rubles")
- "Price on request" for B2B
- "Login to see price" (wholesale customers)
Price history: small graph 30–90 days — trust signal. "Minimum price last 30 days: 3,490 rubles" — like Wildberries marking.
Discount countdown: if discount.ends_at set, show timer. Client-side via setInterval, sync with server time on load.
Availability and Delivery Block
User wants to know: when will I get it? More important than "Buy" button.
- "In stock: 12 pcs" (or "Low: 2 pcs" when qty < 5)
- "Delivery tomorrow" — if order by 18:00 (computed from warehouse schedule)
- "Pickup today" — list nearest points with stock
Delivery date calculation — server logic: warehouse business days, user region, delivery type. Passed as string in API response.
Description Block
Rich text rendered from HTML or markdown. Requirements:
- Long descriptions collapse to 5–6 lines with "Read more" button (CSS
overflow+ JS toggle) - Specs table — separate block with grouped attributes
- Tabs: "Description" / "Specs" / "Reviews" / "Questions"
Tabs via URL hash (#reviews) or search params (?tab=reviews). Allows direct link to reviews — important for SEO.
Reviews Block
Reviews — conversion element and SEO content simultaneously.
Structure:
- Aggregated rating (stars + distribution histogram 1–5)
- "Write review" button (form with rating, text, photo upload)
- Review list with pagination (or lazy load)
- Filters: "With photos", "5 stars", "Latest"
Anti-spam: only authenticated, only buyers (check orders). Moderation — manager queue.
Schema.org: AggregateRating with ratingValue, reviewCount. Affects stars in search results (rich snippets).
CTA — Buy Button
"Buy" / "Add to cart" — main page element. Patterns:
- "Add to cart": adds to cart, user continues browsing. Good for frequent multi-item purchases.
- "Buy now": adds and redirects to checkout. Shorter path for target buyer.
- Sticky CTA: on scroll down appears fixed panel with price and button. Keeps conversion element visible.
On add to cart — feedback: cart icon animation, mini-popup or drawer with confirmation. User feels action completed.
SEO Markup
<!-- Open Graph for social sharing -->
<meta property="og:title" content="Apple MacBook Air 13 M3 — Store Name">
<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"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "127"
}
}
</script>
Related Products
"Similar" and "Frequently bought together" — different algorithms:
- Similar: same category with similar attributes (same price group, same brand or attributes)
- Often together: collaborative filtering — analyze product pairs in orders
Performance
-
LCP (main image):
<img loading="eager" fetchpriority="high">, preload in<head>, WebP with fallback -
CLS (content shift): reserve image space via
aspect-ratioor explicitwidth/height -
Cache: card HTML cached on CDN (Cloudflare) with
s-maxage=300, invalidate via API on change
Timeline
- Basic card (photo, price, variants, button, specs): 2–3 weeks
- Full-featured card (reviews, similar products, sticky CTA, price history, Schema.org, performance): 4–6 weeks







