Home · About · Services · Industries · Projects · Products · Process · Blog · FAQ · Contact
E-commerce performance checklist: from Core Web Vitals to checkout What we check, in order, when an online store feels slow: field data, product images, third-party scripts, server caching, catalogue queries and the checkout…
A store owner sends us a screenshot of a lab score in the 40s and asks for it to be green by next week. Usually the score is the least useful number in the conversation. What matters is how fast product pages load on the phones real customers use, how quickly filters respond, and whether checkout ever stalls. This is the checklist we work through, in the order we work through it.
The short answer: measure real-user Core Web Vitals first, then fix the largest image on product and category pages, cut third-party scripts, cache anonymous catalogue pages at the server or CDN, index the queries behind filters and search, and keep checkout lean. Aim for LCP under 2.5 seconds, INP under 200 milliseconds and CLS under 0.1.
1. Start with field data, not a lab score Lighthouse runs one page load on a simulated device. It is good for debugging and poor for deciding priorities. Google's thresholds for Core Web Vitals are assessed at the 75th percentile of real visits: Largest Contentful Paint (LCP) of 2.5 seconds or less, Interaction to Next Paint (INP) of 200 milliseconds or less, and Cumulative Layout Shift (CLS) of 0.1 or less. INP replaced First Input Delay in 2024, and for stores it is often the metric that fails, because filters, variant pickers and add-to-cart buttons run a lot of JavaScript.
Open the Core Web Vitals report in Search Console. It groups URLs by template, which maps neatly onto home, category, product and content pages. Check the Chrome UX Report data in PageSpeed Insights for your key URLs. It is a rolling 28-day window, so fixes take weeks to show. Split by device. Mobile is usually where the problems are, and usually where most traffic comes from. Collect your own field data if the store has low traffic or you need per-page detail. The small web-vitals library does this in a few lines. import { onLCP, onINP, onCLS } from 'web-vitals';
function send(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
page: location.pathname,
});
navigator.sendBeacon('/rum', body);
}
onLCP(send);
onINP(send);
onCLS(send);Group the results by page template rather than by URL. A store with ten thousand products has one product template, and fixing it fixes all of them.
2. Product and category pages: the LCP image On most product pages the LCP element is the main product photo. On category pages it is a banner or the first row of thumbnails. Most slow LCP we see comes from one of four mistakes: the image is lazy-loaded, it is discovered late because JavaScript inserts it, it is far larger than the space it fills, or it is served without long cache headers.
<img
src="/img/p/1042-800.avif"
srcset="/img/p/1042-400.avif 400w,
/img/p/1042-800.avif 800w,
/img/p/1042-1200.avif 1200w"
sizes="(max-width: 768px) 100vw, 50vw"
width="800" height="800"
fetchpriority="high"
alt="Blue canvas tote bag, front view">Never put loading="lazy" on the main product image or the first row of a grid. Lazy-load everything below the fold instead. Put the image in the server-rendered HTML. A gallery component that loads its first image after hydration delays LCP by the whole JavaScript budget. Generate AVIF or WebP at several widths when images are uploaded, not on request. If you need a JPEG fallback, wrap the image in a picture element. Set width and height (or an aspect-ratio) on every image so the grid does not jump as photos arrive. This is the most common CLS fix on stores. Serve images with a long Cache-Control max-age and a new file name when the image changes. Auto-rotating hero carousels can push LCP later when a larger slide arrives, and rarely earn their weight. A single static banner is faster.
3. Third-party scripts and INP Stores collect scripts: analytics, ad pixels, reviews, live chat, recommendations, a consent banner and an A/B testing tool. Each adds main-thread work that competes with your own code when a shopper taps a filter. Long tasks on the main thread are what make INP fail.
List every third-party script with its owner and purpose. Remove anything nobody can justify. Load scripts with defer or async, and load chat and review widgets only when the visitor scrolls near them or clicks. Prefer a server-side or consolidated tag setup over many separate pixels firing on each page. Avoid client-side A/B tools that hide the page until the experiment loads. They delay rendering for everyone. In Chrome DevTools, record a trace while using filters and the variant picker on a throttled mobile profile, and look for long tasks over 50 milliseconds. For your own code, the usual INP fixes are to update the button state immediately and do the heavy work after the next paint, to avoid re-rendering the whole product grid when one filter changes, and to debounce search-as-you-type requests.
4. Server response time and caching Nothing renders before the HTML arrives. If time to first byte on a category page is over about 800 milliseconds, image work will not save LCP. Most catalogue pages are identical for every anonymous visitor, so they can be cached at NGINX or a CDN for a few minutes and refreshed in the background.
proxy_cache_path /var/cache/nginx/shop levels=1:2 keys_zone=shop:50m
max_size=2g inactive=60m use_temp_path=off;
# Skip the cache for anyone with a session (use your platform's cookie name)
map $cookie_sessionid $skip_cache {
default 1;
"" 0;
}
server {
# ...
location ~ ^/(products|category)/ {
proxy_cache shop;
proxy_cache_valid 200 5m;
proxy_cache_use_stale updating error timeout http_502 http_503;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_cache_bypass $skip_cache;
proxy_no_cache $skip_cache;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://app;
}
}Two details decide whether this works. First, by default NGINX does not cache responses that carry a Set-Cookie header or Cache-Control: private or no-store, so a store that gives every visitor a session cookie on the first page view caches nothing. Create the session only when someone adds to cart or signs in, and check the headers your framework sends. Second, the cart count and the logged-in name in the header must come from a small client-side request, not be baked into the HTML, or the page cannot be shared between visitors.
Stock levels and prices are the usual objection. A five-minute cache on the page is fine if the cart and checkout always read live price and stock from the database, which they must do anyway.
5. Catalogue queries, filters and search Filtered, sorted category pages are where databases struggle as the catalogue grows. A query that was fast with 500 products can take seconds with 50,000 because it now scans the table. Run the real queries with EXPLAIN ANALYZE in PostgreSQL, or explain() in MongoDB, using production-sized data.
-- Filter by category and in-stock, sort by price
CREATE INDEX CONCURRENTLY idx_products_cat_stock_price
ON products (category_id, price, id)
WHERE in_stock;
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, name, price, thumb_url
FROM products
WHERE category_id = 42 AND in_stock
ORDER BY price, id
LIMIT 24;Avoid large OFFSET values for deep pages. Keyset pagination, WHERE (price, id) > (last_price, last_id), stays fast on page 200 and does not skip products that share a price. Precompute filter counts, or cache them per category, instead of counting every facet on every request. Move full-text and typo-tolerant search to a search engine once the database search is slow or poor. Keep the database as the source of truth. Select only the columns the grid needs. Loading full product descriptions to show 24 thumbnails is common and wasteful. 6. Navigation, cart and checkout Shoppers move between category and product pages constantly. Two browser features make those moves feel instant. The back/forward cache restores the previous page from memory, but only if the page is eligible: avoid unload handlers, and avoid Cache-Control: no-store on catalogue pages, which can block it. The DevTools Application panel has a back/forward cache test that lists what blocks it. In Chromium browsers, speculation rules can prefetch product pages before the click.
<script type="speculationrules">
{
"prefetch": [{
"where": { "href_matches": "/products/*" },
"eagerness": "moderate"
}]
}
</script>Never prefetch or prerender cart, checkout or sign-out URLs, since a speculative request must not change state. In checkout itself, load the payment provider's script only on the payment step, keep the page free of marketing scripts, and make sure the order confirmation does not wait on slow work. Sending emails, generating invoices and delivering digital files belong in a background job triggered by the payment webhook, not in the request the customer is waiting on. This matters most for digital goods, such as the Power BI template marketplace we built, where delivery is automated after a Stripe or PayPal payment.
The checklist Field data reviewed per template and device: LCP 2.5 s, INP 200 ms, CLS 0.1 at the 75th percentile. Main product and banner images in server HTML, not lazy-loaded, sized with srcset, fetchpriority="high". Every image has width and height or an aspect ratio. Third-party scripts inventoried, deferred or loaded on interaction; nothing hides the page. Anonymous catalogue pages cached at the edge or NGINX; no session cookie until it is needed. Filter and sort queries checked with EXPLAIN on production-sized data; keyset pagination for deep pages. Catalogue pages eligible for the back/forward cache; product links prefetched where supported. Checkout free of marketing scripts; slow post-payment work moved to background jobs. Performance checked again after every theme change, new plugin or new tracking script. If the field data is green and the store still feels slow, look at the backend: slow admin imports locking tables, cache stampedes after a sale goes live, or a single small server doing image resizing during peak traffic. Those need a look at the whole stack rather than another front-end tweak.
Infigini IT Services · 11 Kanwali Road, Dehradun 248001, India · info@infigini.com · +91 95684 87789