PerformanceMay 20, 20268 min read

HTTP Cache Headers for Ecommerce: Browser Caching Strategy for Product Pages and Assets

Cache-Control headers determine whether browsers and CDNs store your pages and assets between visits. Set them right and repeat visitors load your store in under 1 second. Set them wrong and you're serving your JS bundle fresh on every click.

StoreVitals Team

Browser caching is one of the highest-leverage performance optimizations available to ecommerce stores — and one of the most frequently misconfigured. A returning customer who already loaded your homepage CSS and JavaScript should get those files from their browser cache, not from your server or CDN. Whether they do depends entirely on the Cache-Control headers your server sends.

This guide covers the specific headers that matter, the right values for different asset types, and the caching patterns that apply specifically to ecommerce product pages.

Cache-Control Basics

The Cache-Control header tells browsers and intermediate caches (CDNs, reverse proxies) how long to store a response. The key directives:

  • max-age=N — cache for N seconds. max-age=31536000 = one year.
  • s-maxage=N — CDN-specific max-age. Overrides max-age for shared caches only.
  • no-cache — store the response, but revalidate with the server before using it. Not "don't cache."
  • no-store — don't store anything. True "don't cache."
  • immutable — tells the browser the resource will never change during its max-age. Prevents revalidation requests even on reload.
  • stale-while-revalidate=N — serve the cached (potentially stale) response immediately while fetching a fresh one in the background.
  • must-revalidate — must check with the server once the resource is stale (max-age expired).

The Right Strategy by Asset Type

Static assets with content hashing (JS bundles, CSS files)

Modern build tools (Webpack, Vite, Next.js) add content hashes to filenames: main.a3f9d8.js. When the file changes, the filename changes — so the URL is effectively a different resource. This means you can safely cache these forever:

Cache-Control: public, max-age=31536000, immutable

The immutable directive tells browsers not to revalidate this resource even on a hard reload, since the hash guarantees freshness. This is the optimal setting for any build-tool-generated asset with a content hash in the URL.

Images

Product images rarely change. When they do, the URL typically changes (new upload = new CDN path). Set a long cache with immutable:

Cache-Control: public, max-age=31536000, immutable

If your images use fixed URLs that can be replaced (like /images/product-main.jpg that gets overwritten on update), use a shorter max-age and drop immutable:

Cache-Control: public, max-age=604800

One week is usually right for product images with mutable URLs.

Web fonts

Fonts almost never change. Long cache:

Cache-Control: public, max-age=31536000, immutable

HTML pages (product pages, category pages)

This is where ecommerce caching gets nuanced. Your product pages contain dynamic information: price, stock status, promotional banners. You can't cache them forever — but you also don't want every Googlebot crawl or repeat visitor to wait for a full server-side render.

The standard approach for ecommerce HTML is short CDN caching with stale-while-revalidate:

Cache-Control: public, s-maxage=60, stale-while-revalidate=600

This tells CDNs to cache the page for 60 seconds, then serve the stale version for up to 10 more minutes while fetching a fresh copy in the background. Users almost always get a fast response; the stale window is bounded at 10 minutes. Adjust based on how frequently your prices or stock levels change.

For product pages where price changes are rare (B2B, slow-moving inventory), you can push this further:

Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400

One hour of definite freshness, 24 hours of stale-ok background refresh. Aggressive but valid for stable catalog stores.

Cart, checkout, and account pages

Never cache these at the CDN level. They contain user-specific state:

Cache-Control: private, no-store

Or at minimum:

Cache-Control: private, no-cache

The private directive prevents CDNs from caching the response. no-store prevents the browser from storing it at all.

API responses

Stock availability endpoints, price APIs, and cart APIs should not be cached:

Cache-Control: private, no-store, max-age=0

If you have public API responses that are safe to cache (e.g., a store's open hours that changes once a month), use short max-age with must-revalidate.

The Shopify CDN Advantage

Shopify stores automatically get assets served through Shopify's CDN with correct long-lived cache headers. Theme JS and CSS are content-hashed. Product images are served from cdn.shopify.com with one-year caching. You typically don't need to configure this — Shopify handles it.

What Shopify doesn't cache is the HTML pages themselves. Each product page request hits Shopify's servers (with their own edge caching layer). If TTFB is slow, it's usually the dynamic HTML render, not the assets.

Common Cache Header Mistakes

No-cache on static assets

The most expensive mistake: serving JS/CSS/images with Cache-Control: no-cache or no header at all. Every page load — including the second, third, and hundredth visit — downloads all assets fresh. StoreVitals flags pages where cacheable assets have no Cache-Control header or max-age=0.

Caching HTML pages too aggressively

A product page cached for 24 hours at the CDN will serve the old price for 24 hours after a price change. A sale banner that's active for 4 hours will appear on cached pages for hours after it ends. Match your HTML cache TTL to how frequently your page content actually changes.

Missing Vary headers

If you serve different content to mobile vs. desktop (different HTML, not CSS), you need Vary: User-Agent or risk CDNs serving mobile markup to desktop users. Most modern stores use responsive CSS and a single HTML document — this isn't an issue. But stores with separate mobile sites (/m/ subdomains or m.example.com) need correct Vary configuration.

ETags without max-age

ETags enable conditional requests (If-None-Match) but still require a round-trip to the server to validate. For static assets with content hashes, ETags add complexity without benefit — use max-age=31536000 + immutable instead and skip the validation round-trip entirely.

Checking Your Cache Headers

Browser DevTools → Network tab → click any resource → Headers → Response Headers. Look for Cache-Control in the response.

StoreVitals' Cache Headers Checker fetches any URL and shows the Cache-Control value for the main page and all linked assets, flagging missing or suboptimal values. Run it on your homepage, a product page, and your main JS bundle to see your current caching posture.

HTTP cachingCache-ControlperformanceCDNecommerce performance

See these issues on your store?

Run a free scan and find out in seconds.

Run Free Scan