LCP Optimisation for E-Commerce: Cut Load Time in Half and Recover Lost Revenue
Google's Core Web Vitals research is unambiguous: Largest Contentful Paint (LCP) is the single metric most correlated with real-world user satisfaction and conversion. For e-commerce, the stakes are even higher — product pages are LCP-heavy by nature: hero images, above-the-fold carousels, promotional banners.
The benchmark is 2.5 seconds for a "good" LCP score. Most e-commerce pages fail it not because the servers are slow, but because of a cascade of subtle, fixable issues that compound. This playbook covers exactly those issues.
Understanding the LCP Element
LCP measures the time until the largest visible element in the viewport has finished loading. On a typical product detail page, this is almost always:
- The primary product photo
- A hero banner or carousel
- An above-the-fold promotional block
flowchart TD
A[User navigates to product page] --> B[Browser parses HTML]
B --> C{Is the LCP element\nin initial HTML?}
C -->|No — requires JS| D[⚠️ High LCP risk\nJavaScript must execute first]
C -->|Yes| E[Browser discovers image URL]
E --> F{Preloaded?}
F -->|No| G[Browser queues image request\nbehind CSS/JS resources]
F -->|Yes — rel=preload| H[Image fetched immediately]
G --> I[LCP element loads late]
H --> J[LCP element loads early]
D --> I
The most impactful question to ask about your LCP element: Is it discoverable in the initial HTML, and is it preloaded?
Diagnosing Your LCP Bottleneck
LCP delay has four causes, and knowing which ones affect you determines your fix:
| Cause | What It Looks Like | Typical Fix |
|---|---|---|
| Time To First Byte (TTFB) | LCP starts late even before the element loads | Server caching, CDN, edge rendering |
| Resource Load Delay | LCP element discovered late in parsing | rel=preload, avoid lazy-loading hero image |
| Resource Load Duration | Image itself takes too long to download | Image optimization, AVIF/WebP, sizing |
| Element Render Delay | HTML loaded but element renders late | Eliminate render-blocking CSS/fonts |
Use this script to identify your LCP element programmatically:
// Run in browser console or Playwright evaluate()
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP Element:', lastEntry.element);
console.log('LCP Time:', lastEntry.startTime);
console.log('LCP URL:', lastEntry.url || '(text)');
}).observe({ type: 'largest-contentful-paint', buffered: true });
Fix 1: Never Lazy-Load the Hero Image
This is the single most common LCP regression on e-commerce sites. The product image gets a loading="lazy" attribute from a component library, and the LCP score drops by 800ms overnight.
<!-- ❌ NEVER do this for above-the-fold images -->
<img src="/products/hero.jpg" loading="lazy" alt="Product" />
<!-- ✅ Correct: explicit eager loading + fetchpriority hint -->
<img src="/products/hero.jpg" loading="eager" fetchpriority="high" alt="Product" width="800" height="600" />
In Next.js with the Image component:
// next/image applies lazy loading by default
// For the hero image, always opt out:
import Image from 'next/image';
export function ProductHero({ imageUrl }: { imageUrl: string }) {
return (
<Image
src={imageUrl}
alt="Product"
width={800}
height={600}
priority={true} // ← This sets loading="eager" + fetchpriority="high"
// Do NOT set loading="lazy" here
/>
);
}
Fix 2: Preload the LCP Image
If you know at request time what the LCP image will be (which is true for most product pages), emit a <link rel="preload"> in the <head>:
<head>
<link
rel="preload"
as="image"
href="/products/main-hero.avif"
imagesrcset="/products/main-hero-400w.avif 400w, /products/main-hero-800w.avif 800w"
imagesizes="(max-width: 640px) 400px, 800px"
/>
</head>
In Next.js App Router (using metadata API):
// app/products/[id]/page.tsx
export async function generateMetadata({ params }) {
const product = await getProduct(params.id);
return {
other: {
// Emit preload link via metadata
},
};
}
// Alternatively, emit it directly via a helper component in layout:
// <PreloadHeroImage src={product.heroImageUrl} />
A properly preloaded LCP image typically improves LCP by 300–700ms on fiber connections and much more on mobile networks.
Fix 3: Serve Modern Image Formats
WebP cuts file sizes by 25–34% vs JPEG. AVIF cuts sizes by 50% vs JPEG. Both maintain equivalent visual quality. The browser will pick the best format it supports:
<picture>
<source srcset="/products/hero.avif" type="image/avif" />
<source srcset="/products/hero.webp" type="image/webp" />
<img src="/products/hero.jpg" alt="Product" fetchpriority="high" />
</picture>
For Next.js with a CDN image optimizer, this is handled automatically when you set the formats config:
// next.config.mjs
export default {
images: {
formats: ['image/avif', 'image/webp'], // AVIF preferred
minimumCacheTTL: 60 * 60 * 24 * 30, // 30-day CDN cache
},
};
Fix 4: Eliminate Render-Blocking Resources
CSS in <link> tags blocks rendering until the stylesheet downloads. An oversized or third-party stylesheet can push your LCP element's render by hundreds of milliseconds:
// Playwright test to detect render-blocking resources
import { test, expect } from '@playwright/test';
test('no render-blocking stylesheets greater than 50KB', async ({ page }) => {
const blockingResources: string[] = [];
page.on('response', async (response) => {
const url = response.url();
const headers = response.headers();
if (headers['content-type']?.includes('text/css')) {
const body = await response.body();
if (body.length > 50 * 1024) {
blockingResources.push(`${url} (${(body.length / 1024).toFixed(1)}KB)`);
}
}
});
await page.goto('/products/sample-product');
await page.waitForLoadState('networkidle');
expect(blockingResources, `Large render-blocking stylesheets found:\n${blockingResources.join('\n')}`).toHaveLength(
0,
);
});
Fix 5: Improve TTFB with Edge Caching
For e-commerce, TTFB is often the hidden LCP killer. Product pages that require fresh database queries can have TTFB of 500ms+. Solutions:
| Strategy | Latency Reduction | Complexity |
|---|---|---|
| CDN static caching | Up to 95% | Low |
| Edge function with stale-while-revalidate | 60–80% | Medium |
| ISR (Next.js Incremental Static Regeneration) | 70–90% | Low |
| Short-lived server-side cache (Redis) | 50–70% | Medium |
For product pages with infrequent data changes, ISR is the lowest-effort highest-impact option:
// app/products/[id]/page.tsx
export const revalidate = 300; // Revalidate cache every 5 minutes
export default async function ProductPage({ params }) {
const product = await getProduct(params.id);
return <ProductView product={product} />;
}
Automated LCP Regression Testing
Catch LCP regressions before they reach production. Run Lighthouse programmatically in CI:
// tests/performance/lcp.test.ts
import lighthouse from 'lighthouse';
import chromeLauncher from 'chrome-launcher';
test('product page LCP is under 2500ms', async () => {
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
const result = await lighthouse(`${process.env.BASE_URL}/products/test-product`, {
port: chrome.port,
onlyCategories: ['performance'],
formFactor: 'mobile',
throttlingMethod: 'simulate',
});
await chrome.kill();
const lcp = result.lhr.audits['largest-contentful-paint'];
const lcpMs = lcp.numericValue;
console.log(`LCP: ${lcpMs.toFixed(0)}ms (score: ${lcp.score})`);
expect(lcpMs).toBeLessThan(2500);
}, 60_000);
The LCP Improvement Checklist
[ ] Hero image has loading="eager" and fetchpriority="high"
[ ] Hero image has <link rel="preload"> in <head>
[ ] Serving AVIF or WebP (not just JPEG/PNG)
[ ] Images have explicit width/height attributes (prevents layout shift)
[ ] No large render-blocking stylesheets
[ ] TTFB < 600ms (use ISR or edge caching)
[ ] LCP element is in initial HTML (not injected by JS)
[ ] Automated Lighthouse test in CI pipeline
[ ] LCP monitored on RUM (Real User Monitoring) in production
Further Reading
- Core Web Vitals — web.dev: Google's authoritative guide to LCP, INP, and CLS metrics with measurement tools and optimization techniques
- Optimize LCP — web.dev: Step-by-step LCP optimization guide covering resource loading, rendering, and element-specific improvements
- Core Web Vitals Report in Search Console: How to interpret your real-user Core Web Vitals data from Google Search Console
- PageSpeed Insights API: Integrate Lighthouse-based performance analysis directly into your CI/CD pipeline
Related articles: Also see the complete 2026 guide to web performance optimisation, improving TTFB as the server-side metric that directly drives LCP, and reducing bundle size to unblock LCP element rendering.
If your team is deploying changes to product pages regularly, you need LCP monitoring that reflects real user experiences — not just a one-time Lighthouse audit. ScanlyApp can be configured to run automated performance checks on your product pages after every deployment.
Catch LCP regressions before your users do: Try ScanlyApp free and set up automated Core Web Vitals monitoring on your product pages.
