PerformanceJune 17, 202610 min read

CDN Edge Caching Strategy for Headless Shopify Stores in 2026

The cache-control headers, stale-while-revalidate patterns, and Vary header pitfalls that determine whether a headless Shopify store performs at CDN speed or origin speed. Specific configurations for Hydrogen, custom Next.js, and edge frameworks.

StoreVitals Team

The single biggest performance lever for a headless Shopify store in 2026 isn't a framework choice or a hosting provider — it's the cache-control strategy. A correctly configured edge cache delivers 30–80ms TTFB globally. A misconfigured one delivers 300–800ms TTFB from origin, regardless of how fast your origin actually is. This article documents the cache-control headers, stale-while-revalidate patterns, and Vary header pitfalls specific to ecommerce, with concrete configurations for Hydrogen v2, custom Next.js storefronts, and edge framework stores.

Why Caching Is the Lever for Headless Ecommerce

Shopify Liquid stores serve pages from Shopify's CDN by default. Edge caching is automatic, invalidation is handled by Shopify, and the typical TTFB is 30–80ms globally. The merchant doesn't need to think about it.

Headless stores opt out of this automatic caching. Whether you're using Hydrogen (Shopify's official headless framework), a custom Next.js or Remix storefront on Vercel, or an edge-rendered storefront on Cloudflare Workers or Netlify, the responsibility for cache configuration moves to your team. Done well, headless can match or exceed Liquid's edge performance. Done poorly, headless TTFB can be 5–10x slower than the Liquid baseline.

The performance gap matters for SEO (Google's CWV ranking factor uses real user TTFB), for conversion (every 100ms TTFB improvement correlates with ~1% conversion lift in published ecommerce studies), and for cost (uncacheable requests scale linearly with traffic, while edge-cached requests are nearly free at scale).

The Cache-Control Headers That Matter

public, max-age=N. The response can be cached by any cache (browser, CDN, intermediate proxy) for N seconds. Use for content that doesn't change per user.

private, max-age=N. The response can be cached only by the user's browser, not by shared caches (CDN). Use for personalized content where each user sees different data (cart, account pages).

s-maxage=N. Overrides max-age for shared caches (CDN). Lets you set a different cache duration at the edge vs. browser. Common pattern: public, max-age=60, s-maxage=3600 — browser caches 1 minute, CDN caches 1 hour.

stale-while-revalidate=N. The cached response can be served stale for N seconds after expiry while the cache refreshes in the background. This is the most important header for ecommerce: it allows the cache to serve fast responses even at cache miss boundaries.

stale-if-error=N. If the origin is down or returns 5xx, the cache can serve stale content for N seconds. Essential for resilience.

must-revalidate. Forces revalidation when cache expires (no serving stale). Use only when stale content would cause real problems.

Vary: header-name. Cache should store separate copies of the response for different values of the named request header. Vary: Cookie creates a separate cache entry per unique cookie value — usually catastrophic for cache hit rate. Vary: Accept-Encoding is safe and standard.

The Ecommerce Cache Strategy by Page Type

Homepage: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400. Update at most every 5 minutes browser-side, cache 1 hour at CDN, serve stale for up to a day if revalidation fails. Homepages change occasionally (hero swap, featured product update) but should be aggressively cached.

Category/collection pages: public, max-age=180, s-maxage=1800, stale-while-revalidate=3600. 3 minutes browser, 30 minutes CDN, 1 hour stale-while-revalidate. Category content changes when products are added/removed; stale-while-revalidate ensures inventory updates propagate within minutes without hurting performance.

Product detail pages (in-stock): public, max-age=120, s-maxage=600, stale-while-revalidate=3600. 2 minutes browser, 10 minutes CDN, 1 hour stale-while-revalidate. Inventory changes need to propagate quickly; price changes need to propagate quickly. The stale-while-revalidate window allows fast responses even when inventory just changed.

Product detail pages (out-of-stock): Same as in-stock but consider shorter s-maxage if back-in-stock alerts are common.

Search results pages: public, max-age=60, s-maxage=300, stale-while-revalidate=600. Search queries vary widely; cache the popular queries for 5 minutes at edge.

Account pages, cart, checkout: private, no-cache. Never cache personalized pages at the edge.

Static assets (images, fonts, CSS, JS): public, max-age=31536000, immutable. Cache for one year. Use file-hash-based filenames so any change generates a new URL that bypasses cache.

Sitemap.xml, robots.txt: public, max-age=3600. Update hourly.

The Vary Header Pitfalls That Destroy Cache Hit Rate

Vary headers tell the cache to store separate response copies for different request header values. Used wrong, they fragment the cache into per-user copies and effectively disable caching.

Pitfall 1: Vary: Cookie. Setting Vary: Cookie tells the cache to store separate copies per unique cookie combination. Since most ecommerce sites set tens of cookies (analytics, session, personalization), each visitor has a unique cookie combination — cache hit rate collapses to near zero. Headless frameworks sometimes set this header accidentally; audit and remove.

Pitfall 2: Vary: User-Agent. Older mobile-detection patterns Vary on User-Agent to serve different mobile HTML. Modern responsive design eliminates this need. Vary: User-Agent fragments the cache by every browser version. Avoid.

Pitfall 3: Vary: Accept-Language. Sometimes appropriate for multi-language stores, but make sure your language detection is deterministic (e.g., based on URL path or geo, not Accept-Language header) to prevent unnecessary fragmentation.

Pitfall 4: Implicit Vary on personalization platform cookies. Some platforms set cookies that downstream cache rules interpret as cache-fragmenting. Audit your CDN cache rules for any "vary by cookie" entries.

Stack-Specific Configurations

Hydrogen v2 on Oxygen (Shopify's hosting):

// In a loader function
export async function loader({ context, request }: LoaderArgs) {
  const data = await context.storefront.query(PRODUCT_QUERY, {
    variables: { handle: params.handle },
  });

  return json(data, {
    headers: {
      'Cache-Control': 'public, max-age=120, s-maxage=600, stale-while-revalidate=3600',
    },
  });
}

Oxygen respects these cache-control headers at edge automatically. The cache key is the URL by default; do not set Vary on cookies.

Next.js App Router on Vercel:

// In page.tsx or a route handler
export const revalidate = 600; // 10-minute ISR

// Or per-fetch:
const product = await fetch(`${API}/products/${slug}`, {
  next: { revalidate: 600 },
});

Vercel's edge network respects revalidate via ISR (Incremental Static Regeneration). For more fine-grained control, set response headers directly in route handlers using Response.headers.

Remix on Cloudflare Pages:

export async function loader({ request, context }: LoaderArgs) {
  const data = await fetchProduct(params.handle);

  return json(data, {
    headers: {
      'Cache-Control': 'public, max-age=120, s-maxage=600, stale-while-revalidate=3600',
      'CDN-Cache-Control': 'max-age=600',
    },
  });
}

Cloudflare honors standard Cache-Control headers. CDN-Cache-Control lets you specify edge-specific behavior without affecting browser caching.

Custom edge worker (Cloudflare Workers, Deno Deploy): Set response headers explicitly. Use cache API for fine-grained control:

const cache = caches.default;
const cacheKey = new Request(request.url);
let response = await cache.match(cacheKey);

if (!response) {
  response = await fetch(originUrl);
  response = new Response(response.body, response);
  response.headers.set(
    'Cache-Control',
    'public, max-age=600, stale-while-revalidate=3600'
  );
  ctx.waitUntil(cache.put(cacheKey, response.clone()));
}
return response;

Inventory Invalidation — The Hardest Problem

Edge caching wins on read performance but creates a gap on write propagation. When inventory changes, cached pages may show wrong availability until cache expires. Three patterns address this:

1. Aggressive stale-while-revalidate. Short max-age, long stale-while-revalidate. Cache always serves stale fast, but revalidation happens on every request after expiry. Inventory propagates within seconds to most users.

2. Webhook-triggered cache purge. Subscribe to Shopify inventory webhooks. On each webhook, call your CDN's purge API to invalidate specific product page URLs. Cloudflare, Vercel, and Fastly all support this. Latency: 1–5 seconds from webhook to global purge.

3. Edge cache tags. Cloudflare Enterprise and Fastly support cache tags — tag responses with logical groupings (e.g., "product:1234"), then purge all responses with a specific tag in one API call. Useful for purging all pages featuring a single product.

Most stores can achieve acceptable inventory propagation with pattern 1 alone. Patterns 2 and 3 are for stores with flash-sale or BFCM workloads where seconds matter.

Measuring Cache Performance

Cache hit ratio. Your CDN dashboard shows hit ratio per route. Target: >90% on product/category pages, >95% on static assets. Below 70% indicates a Vary or cache-control misconfiguration.

TTFB from real users. Google Search Console > Core Web Vitals > TTFB. Target: <800ms p75. Below 200ms indicates edge caching is working globally.

WebPageTest from multiple regions. Run from US-East, Europe, Asia. TTFB diff across regions reveals whether you're serving from edge (small diff) or origin (large diff).

Origin request rate. Your hosting bill includes function invocations or compute time. If origin requests grow linearly with traffic, caching isn't working. Edge-cached sites see origin requests scale sub-linearly.

What StoreVitals Detects

Our crawler measures TTFB from multiple regions and surfaces:

  • TTFB per region (geographic CDN distribution audit)
  • Cache-Control headers per response
  • Vary header detection (with warnings for problematic Vary values)
  • Static asset cache-max-age compliance
  • Estimated cache hit ratio based on response headers

Run a free scan and check the "Performance" pillar. TTFB above 500ms or Cache-Control headers missing on cacheable pages flag immediately. For headless Shopify stores, our Premium $79 audit includes a full cache strategy review with stack-specific configuration recommendations.

CDNcachingheadlessShopify Hydrogenperformance

See these issues on your store?

Run a free scan and find out in seconds.

Run Free Scan