Back to Blog

LCP Optimisation for E-Commerce: Cut Load Time in Half and Recover Lost Revenue

Largest Contentful Paint directly impacts conversion rates — every 100ms of LCP improvement can increase conversions by up to 1%. This playbook walks through diagnosing LCP bottlenecks on e-commerce product pages and implementing fixes that move the metric from red to green.

Published

7 min read

Reading time

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

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.

Related Posts

Testing CDN Caching Rules and Cache Invalidation: A Developer's Guide
Performance & Scalability
6 min read

Testing CDN Caching Rules and Cache Invalidation: A Developer's Guide

CDN misconfiguration is one of the hardest bugs to catch in QA — it works perfectly in staging (which bypasses the CDN) but fails in production. Learn how to test cache headers, validate invalidation logic, and build automated checks that keep your caching layer honest.