Page speed is not just a technical metric — it directly correlates with conversion rates, bounce rates, and revenue. Research consistently shows that every 100ms reduction in page load time can increase conversion rates by up to 1%. For a Shopify store doing £1M in annual revenue, shaving half a second off your page load could be worth £50,000 or more per year.

The good news is that Shopify provides a robust CDN infrastructure out of the box. The bad news is that most store owners and developers do not understand how it works, what they can control, and where the real performance bottlenecks lie. This guide covers the technical reality of edge delivery on Shopify, the optimisation techniques that actually move the needle, and how to measure the impact of your changes. This builds on the Core Web Vitals work we do as part of our Shopify development services.

How Shopify’s CDN works

Shopify uses Cloudflare as its CDN provider. This is not something you configure or pay extra for — it is baked into the platform. Every Shopify store, regardless of plan, benefits from Cloudflare’s global network of data centres.

However, the CDN does not cache everything equally. Understanding what gets cached at the edge versus what requires an origin server round-trip is fundamental to Shopify performance optimisation.

What the CDN caches

  • Static assets — CSS files, JavaScript files, fonts, and images served from cdn.shopify.com are cached aggressively at edge locations worldwide. These use content-hashed URLs (fingerprinting), so cache invalidation happens automatically when files change.
  • Product images — all images uploaded to Shopify are served through the CDN with automatic format conversion (WebP/AVIF where supported) and on-the-fly resizing.
  • Theme assets — any file in your theme’s assets/ directory gets CDN delivery with long cache TTLs.

What the CDN does not cache

  • HTML pages — the actual page content generated by Liquid templates is not cached at the CDN edge on standard Shopify. Each page request goes to Shopify’s origin servers, which execute the Liquid template, query product data, and return rendered HTML.
  • Cart and checkout pages — these are always dynamic and personalised per session.
  • Authenticated content — customer account pages, B2B pricing, and other session-dependent content.

The single biggest performance misconception in the Shopify ecosystem is that the CDN makes everything fast. It makes static assets fast. Your HTML delivery speed is determined by Shopify’s origin server performance and the complexity of your Liquid templates.

Diagram showing Shopify CDN architecture with edge cached assets and origin-served HTML
Shopify’s CDN caches static assets at the edge globally but serves HTML from origin servers. Understanding this distinction is key to effective performance optimisation.

Asset delivery and fingerprinting

Shopify automatically fingerprints theme assets. When you reference a file like {{ 'theme.css' | asset_url }}, Shopify generates a URL like:

https://cdn.shopify.com/s/files/1/0123/4567/8901/t/1/assets/theme.css?v=123456789

The query parameter is a content hash. When you update the file, the hash changes, the URL changes, and the browser fetches the new version. The old version remains cached (and eventually expires) while the new version is served immediately.

This is an effective cache-busting strategy, but it only works for files served through Shopify’s asset pipeline. If you are loading scripts from external domains, you need to manage cache-busting yourself.

Asset loading order matters

The order in which your browser discovers and downloads assets has a significant impact on perceived performance. The critical rendering path — the sequence of steps required to render the first pixel on screen — should be as short as possible:

<!-- Optimal asset loading order in theme.liquid -->
<head>
  <!-- 1. Critical CSS inline (above-the-fold styles) -->
  <style>
    /* Only styles needed for initial viewport */
    .header { ... }
    .hero { ... }
  </style>

  <!-- 2. Preload critical assets -->
  <link rel="preload" href="{{ 'theme.css' | asset_url }}" as="style">
  <link rel="preload" href="{{ 'hero-image.jpg' | image_url: width: 1200 }}" as="image">

  <!-- 3. Full stylesheet (non-blocking if critical CSS is inlined) -->
  <link rel="stylesheet" href="{{ 'theme.css' | asset_url }}">

  <!-- 4. Defer non-critical scripts -->
  <script src="{{ 'theme.js' | asset_url }}" defer></script>
</head>

Image optimisation at the edge

Shopify’s image CDN is one of the most powerful features of the platform. It provides on-the-fly image transformation without requiring any third-party service or build step.

Automatic format negotiation

When a browser requests an image from Shopify’s CDN, the CDN checks the Accept header and automatically serves the optimal format. Modern browsers receive WebP or AVIF (significantly smaller than JPEG/PNG), while older browsers receive the original format. You do not need to generate multiple format versions — it happens at the edge.

Responsive image URLs

Use Shopify’s image_url filter to generate size-specific URLs:

<!-- Generate responsive srcset for product images -->
{%- assign image = product.featured_image -%}
<img
  src="{{ image | image_url: width: 800 }}"
  srcset="
    {{ image | image_url: width: 400 }} 400w,
    {{ image | image_url: width: 600 }} 600w,
    {{ image | image_url: width: 800 }} 800w,
    {{ image | image_url: width: 1200 }} 1200w,
    {{ image | image_url: width: 1600 }} 1600w
  "
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  alt="{{ image.alt | escape }}"
  width="{{ image.width }}"
  height="{{ image.height }}"
  loading="lazy"
  decoding="async"
>

The CDN generates and caches each size variant on first request. Subsequent requests for the same size are served from the edge cache with no processing delay.

Image quality parameters

Shopify does not expose a quality parameter in the standard image_url filter. The CDN applies its own quality settings based on format and size. If you need more control over compression, consider pre-processing images before upload or using Shopify’s image_url with specific crop parameters.

Comparison of image formats and file sizes served by Shopify CDN
Shopify’s CDN automatically negotiates the best image format. A 500KB JPEG might be served as a 120KB WebP or 90KB AVIF to supporting browsers.

Cache control and invalidation

Understanding Shopify’s cache headers helps you identify performance issues and work within the platform’s constraints.

Asset cache headers

Static assets from cdn.shopify.com typically have these cache headers:

Cache-Control: public, max-age=31536000, immutable
ETag: "abc123"
Vary: Accept-Encoding

The max-age=31536000 (one year) and immutable directive mean the browser will never revalidate these assets until the URL changes. This is why content-hashed URLs are critical — they are the cache invalidation mechanism.

HTML page cache headers

HTML pages from Shopify have much shorter cache lifetimes:

Cache-Control: max-age=0, must-revalidate, no-transform
ETag: "def456"
Vary: Accept-Encoding, Accept-Language

The max-age=0, must-revalidate means every page request checks with the origin server. If the page has not changed, the server returns a 304 Not Modified (no body), which is fast. If it has changed, the full HTML is returned.

Section rendering API caching

Shopify’s Section Rendering API allows you to fetch individual sections via AJAX. These responses have their own caching behaviour and can be used to build partially dynamic pages where most content is static but specific sections update dynamically (for example, cart count, wishlist status).

// Fetch a specific section without full page reload
fetch('/products/example-product?sections=product-recommendations')
  .then(response => response.json())
  .then(sections => {
    document.getElementById('recommendations').innerHTML =
      sections['product-recommendations'];
  });

HTTP/2, HTTP/3, and connection management

Shopify’s CDN supports HTTP/2 and HTTP/3 (via QUIC). This has significant performance implications:

  • Multiplexing — multiple requests share a single TCP connection, eliminating the need for domain sharding or sprite sheets.
  • Header compression — HPACK/QPACK compression reduces overhead on repeated requests to the same domain.
  • Server push — though Shopify does not currently expose server push configuration, the protocol supports it.
  • 0-RTT connection resumption — HTTP/3 with QUIC can resume connections without a full handshake, significantly reducing latency for returning visitors.

The practical implication: consolidating your assets onto fewer domains is still beneficial (fewer DNS lookups, fewer TLS handshakes), but the old practice of splitting assets across multiple domains to increase parallel downloads is counterproductive with HTTP/2+.

Resource hints and preloading

Resource hints tell the browser about resources it will need before it discovers them naturally through parsing. On Shopify, these are valuable because Liquid templates often produce complex dependency chains that the browser cannot predict.

Preconnect

Establish early connections to known third-party origins:

<!-- In theme.liquid <head> -->
<link rel="preconnect" href="https://cdn.shopify.com" crossorigin>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

{%- comment -%} Only preconnect to domains you actually use on most pages {%- endcomment -%}
{%- if settings.klaviyo_enabled -%}
  <link rel="preconnect" href="https://static.klaviyo.com">
{%- endif -%}

Preload

Preload resources that are critical for above-the-fold rendering:

<!-- Preload the LCP image -->
{%- if template == 'index' -%}
  <link rel="preload" as="image" href="{{ section.settings.hero_image | image_url: width: 1200 }}">
{%- endif -%}

<!-- Preload critical font -->
<link rel="preload" as="font" type="font/woff2" href="{{ 'heading-font.woff2' | asset_url }}" crossorigin>

Fetchpriority

The fetchpriority attribute gives the browser more explicit hints about resource importance:

<!-- High priority for LCP image -->
<img src="{{ hero_image | image_url: width: 1200 }}" fetchpriority="high" loading="eager">

<!-- Low priority for below-fold images -->
<img src="{{ product.featured_image | image_url: width: 400 }}" fetchpriority="low" loading="lazy">
Timeline comparison showing page load with and without resource hints
Resource hints can reduce perceived load time by 200-500ms by parallelising asset discovery with HTML parsing.

Edge computing on Shopify Plus

Shopify Plus merchants can deploy a reverse proxy or edge function layer between the user and Shopify’s origin. This unlocks capabilities that are impossible on standard Shopify:

  • HTML caching at the edge — cache rendered HTML pages at edge locations, serving them without origin round-trips. Invalidate on product changes via webhooks.
  • A/B testing at the edge — route users to different theme variations without client-side JavaScript flicker.
  • Geographic routing — serve country-specific content or redirect to localised storefronts based on the user’s location.
  • Custom security headers — inject CSP, HSTS, and other security headers at the edge.
  • Bot management — block malicious bots before they reach Shopify’s servers.
// Cloudflare Worker: Cache Shopify HTML at the edge
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const url = new URL(request.url);

  // Skip caching for cart, checkout, account pages
  const noCachePaths = ['/cart', '/checkout', '/account'];
  if (noCachePaths.some(p => url.pathname.startsWith(p))) {
    return fetch(request);
  }

  // Check edge cache first
  const cache = caches.default;
  let response = await cache.match(request);

  if (!response) {
    response = await fetch(request);
    const newResponse = new Response(response.body, response);
    newResponse.headers.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=600');
    event.waitUntil(cache.put(request, newResponse.clone()));
    return newResponse;
  }

  return response;
}

This approach caches HTML pages for 5 minutes with a 10-minute stale-while-revalidate window, dramatically reducing TTFB for most page loads while keeping content reasonably fresh.

International performance considerations

For Shopify stores selling internationally, edge delivery performance varies by geography. Cloudflare’s network has strong coverage in Europe and North America but thinner coverage in some parts of Asia, Africa, and South America.

When targeting international markets through B2B channels or direct-to-consumer, consider:

  • TTFB by region — measure Time to First Byte from your target markets, not just from the UK. Tools like WebPageTest allow you to test from specific global locations.
  • Third-party script geography — a script hosted on a US-only server adds significant latency for users in Asia or Australia. Check where your third-party services are hosted.
  • Image sizes by market — mobile network speeds vary dramatically by country. What is acceptable on UK 5G may be unusable on 3G connections in emerging markets.
  • Font subsetting — if you serve localised content in multiple languages, subset your fonts to include only the characters needed for each market. A full CJK font file can be 5-10MB.

Performance monitoring and measurement

You cannot improve what you do not measure. Effective performance monitoring for Shopify stores requires both synthetic testing and real user monitoring (RUM).

Synthetic testing

Synthetic tests run from controlled environments with consistent network conditions. They are excellent for tracking changes over time and benchmarking against competitors:

  • Lighthouse — built into Chrome DevTools, provides scores for Performance, Accessibility, Best Practices, and SEO.
  • WebPageTest — detailed waterfall analysis from multiple global locations with various connection speeds.
  • Google PageSpeed Insights — combines Lighthouse with CrUX (Chrome User Experience Report) field data.

Real user monitoring

RUM captures actual user experience data from real visitors on real devices and connections. This is far more representative than synthetic testing:

// Basic RUM implementation using Performance API
if ('PerformanceObserver' in window) {
  // Measure Largest Contentful Paint
  new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    const lastEntry = entries[entries.length - 1];
    console.log('LCP:', lastEntry.startTime);
    // Send to your analytics endpoint
  }).observe({ type: 'largest-contentful-paint', buffered: true });

  // Measure First Input Delay
  new PerformanceObserver((entryList) => {
    for (const entry of entryList.getEntries()) {
      console.log('FID:', entry.processingStart - entry.startTime);
    }
  }).observe({ type: 'first-input', buffered: true });

  // Measure Cumulative Layout Shift
  new PerformanceObserver((entryList) => {
    let clsValue = 0;
    for (const entry of entryList.getEntries()) {
      if (!entry.hadRecentInput) clsValue += entry.value;
    }
    console.log('CLS:', clsValue);
  }).observe({ type: 'layout-shift', buffered: true });
}
Dashboard showing Core Web Vitals metrics from real user monitoring
Real user monitoring provides field data that reflects actual customer experience, unlike synthetic tests which run in controlled conditions.

Edge delivery optimisation checklist

Based on our Shopify development experience, here is a prioritised checklist for edge delivery optimisation:

  1. Audit your asset loading waterfall — use WebPageTest to identify render-blocking resources, excessive DNS lookups, and unnecessary connection overhead.
  2. Implement responsive images with srcset — every product and content image should serve size-appropriate versions.
  3. Add preconnect hints — for every third-party domain loaded on the majority of pages.
  4. Preload LCP resources — identify your Largest Contentful Paint element on each template and preload it.
  5. Defer non-critical JavaScript — analytics, marketing pixels, and interactive features should not block initial render.
  6. Inline critical CSS — extract above-the-fold styles and inline them in <head>.
  7. Use fetchpriority — explicitly prioritise your LCP image and deprioritise below-fold images.
  8. Minimise third-party scripts — audit your app stack and remove anything not delivering measurable value.
  9. Consider edge caching (Plus) — if your TTFB is consistently above 500ms, a reverse proxy with edge HTML caching can halve it.
  10. Implement RUM — measure real user performance, not just lab metrics.

Edge delivery optimisation on Shopify is about understanding the platform’s architecture and working within its constraints. You cannot change how Shopify’s origin servers work, but you can control how assets are loaded, how images are served, and how the browser discovers and prioritises resources. On Plus, the additional edge computing layer opens up HTML caching and custom logic that can dramatically improve global performance.

The key is measurement. Before optimising anything, establish baselines using both synthetic testing and real user monitoring. After every change, measure again. Not every optimisation that improves lab scores improves real user experience, and some changes that look negligible in lab testing have outsized impact in the field.

If you need help optimising your Shopify store’s performance, get in touch. Performance optimisation is a core part of our Shopify development work.