Every second of load time costs e-commerce stores real money. Not in a vague "conversion rate optimization" way. In a very specific way: Farfetch found a 1.3% conversion increase for every 100ms of LCP improvement. Amazon found that every 100ms of latency cost them 1% in sales. These are not small numbers.
E-commerce sites have Core Web Vitals problems that standard blogs do not have. Product image variants, dynamic pricing, cart state, review widgets, inventory notifications — all of these add complexity that breaks CWV in specific ways. This guide covers those specific breakages.
Why E-Commerce Sites Are Harder to Optimize
A blog post has one hero image and mostly static content. A product page has:
- 5-20 product images that swap on variant selection
- Dynamic price that might change based on membership, location, or promotions
- Cart badge that shows current item count (requires JavaScript)
- Review widget that loads after the main content
- Stock indicator that checks inventory in real time
- Recently viewed products (personalized, cannot be cached)
- Upsell recommendations (also personalized)
Every one of these is a potential source of CLS, LCP delays, or slow INP. Let us go through each Core Web Vital specifically for e-commerce.
LCP on Product Pages
Your LCP element is almost always the main product hero image. This is good news because you have control over it. The specific mistakes that hurt LCP on product pages:
The Product Image Gets Lazy Loaded
This is the same mistake we see on Shopify stores and regular sites alike. The product image should never be lazy loaded. It is the LCP element. Lazy loading it adds 500ms to 2 seconds to your LCP depending on connection speed.
<!-- Wrong: lazy loading the hero product image -->
<img src="product-main.webp" loading="lazy" alt="Product" />
<!-- Right: priority loading for the main product image -->
<img
src="product-main.webp"
loading="eager"
fetchpriority="high"
width="600"
height="600"
alt="Product name"
/>
Product Images Not in WebP Format
If your product images are JPEG or PNG, you are potentially doubling your image payload versus WebP. On a product page with a 400KB JPEG hero, switching to WebP typically brings it to 80-120KB with no visible quality difference. That directly cuts LCP by 300-600ms on mobile.
Multiple Product Images Loading Simultaneously
Product pages often show thumbnail gallery images below the main image. These secondary images should be lazy loaded. Only the main product hero needs eager loading. Everything else can wait:
<!-- Main product image: eager -->
<img src="product-main.webp" loading="eager" fetchpriority="high" ... />
<!-- Thumbnail gallery: lazy load all of these -->
<img src="product-angle-2.webp" loading="lazy" ... />
<img src="product-angle-3.webp" loading="lazy" ... />
<img src="product-angle-4.webp" loading="lazy" ... />
CLS on E-Commerce: The Many Ways Content Shifts
E-commerce has more CLS sources than almost any other type of site. Here are the specific ones to fix:
Dynamic Price Injection
Sites that show personalized pricing (member discounts, geo-based prices, bulk pricing) often load the default price from the server and then update it via JavaScript. This causes a layout shift if the new price is a different size than the placeholder.
Fix: Reserve exact space for the price. Use a skeleton with the same height as the largest price you might show. Or better, use CSS to ensure all price variants take the same space:
/* Prevent price CLS by reserving space */
.product-price {
min-height: 2.5rem; /* Reserve space even before JS loads */
display: flex;
align-items: center;
}
/* Skeleton while price loads */
.product-price.loading {
background: #e2e8f0;
border-radius: 4px;
width: 80px;
animation: pulse 1.5s ease-in-out infinite;
}
Review Widget Loading After Content
Third-party review widgets (Trustpilot, Yotpo, Okendo, Judge.me) load asynchronously and push content down. Fix this by reserving the exact height of the reviews section before it loads:
<div class="reviews-container" style="min-height: 400px">
<!-- Review widget loads here -->
<div id="review-widget" data-product-id="12345"></div>
</div>
Add to Cart Button Size Change
Buttons that change size when JavaScript loads (from a plain HTML button to a styled component with icons and animations) cause CLS. Pre-style your buttons with the final CSS so the size does not change when JS loads:
<!-- Pre-style with explicit height so no shift when JS enhances it -->
<button
class="add-to-cart"
style="height: 48px; width: 100%; padding: 0 24px;"
>
Add to Cart
</button>
INP on E-Commerce: The Interaction Problems
Variant Selection Causing Full Re-Renders
When a user selects a different color or size, the entire product page often re-renders to show the right image and check stock. On a React or Vue app, this can be 200-500ms of processing time depending on how the state is managed.
The fix: scope your state to only the parts that need to change. The product description does not change when a user selects a different color. The product FAQ does not change. Only the image gallery, price, and stock status need to update. Isolate those into their own state containers so variant selection only triggers re-renders in the parts that actually change.
Add to Cart Interaction Speed
The add-to-cart interaction is your most important button interaction. Users expect immediate feedback when they click it. If your cart update takes 400ms because it runs a synchronous API call before showing any feedback, users will click it again thinking it did not work.
Fix: show optimistic UI instantly, then update in the background:
async function addToCart(productId) {
// Update UI immediately (optimistic update)
setCartCount(prev => prev + 1);
setButtonState('added');
// Then do the actual API call in background
try {
await api.addToCart(productId);
} catch (error) {
// Rollback if it failed
setCartCount(prev => prev - 1);
setButtonState('error');
}
}
Category and Listing Page Optimization
Product Grid CLS
Product listing pages with image grids are a common CLS source. If you do not set image dimensions, the grid reflows as images load. Set explicit aspect ratios:
.product-card-image {
aspect-ratio: 1 / 1; /* Square product images */
width: 100%;
object-fit: cover;
background: #f1f5f9; /* Placeholder color while loading */
}
Infinite Scroll vs Pagination
Infinite scroll adds content below the viewport as users scroll, which causes CLS if not done carefully. The content insertion point should always be below the current scroll position, never above. If you prepend items (going back up in the list), this causes content shifts for everything already on screen.
Platform-Specific Issues
| Platform | Biggest CWV Problem | Typical Fix |
|---|---|---|
| Shopify | Hero image lazy-loaded by default | Remove loading="lazy" from hero in theme code |
| WooCommerce | Plugin bloat, slow TTFB from heavy PHP | Object caching (Redis), page caching, disable unused plugins |
| Magento/Adobe Commerce | TTFB often 1-3s from complex server-side rendering | Full page cache configuration, Varnish, CDN |
| BigCommerce | Third-party apps, script manager overhead | Script Manager placement (footer, defer), app audit |
FAQ
How much does improving CWV actually help conversion rate?
The numbers vary by store, but multiple studies show meaningful correlation. Farfetch saw 1.3% conversion increase per 100ms LCP improvement. COOK saw 7% less bounces after reducing page load time. Zalando saw 0.7% revenue increase per 100ms improvement. These are not guaranteed for every store, but the direction is consistent: faster pages convert better.
My product pages have 15+ images. How do I handle them all?
Eager load only the first visible image (the main hero). Lazy load everything else. Use srcset to serve appropriately sized images for the device. Consider an image CDN (Cloudinary, Imgix, Cloudflare Images) that handles WebP conversion, resizing, and delivery automatically.
Should I cache personalized pages?
Do not cache the full page if it contains user-specific content. Instead, serve the skeleton/shell from cache and fetch personalized content (price, cart status, recommendations) via API after load. This keeps TTFB fast while still showing accurate personalized data.
Related Performance Guides
Still Getting Red Scores?
Run a free audit and get a punch-list of exactly what to fix. No account needed.
Run Free Audit →Still Getting Red Scores?
Run your site through VitalsFixer. Free audit in 30 seconds, no account needed.
Analyze My Site Free →Want an Expert to Handle It?
Real engineers, 48-hour turnaround, money back if scores don't improve.
View Expert Fix →