Building a Global Edge Cache Layer: Cloudflare Workers, KV, and Smart Invalidation

A practical look at edge computing architecture, when it's worth adopting, and a working example of edge function deployment.

Beyond CDN Caching: Programmable Edge Caching

Traditional CDN caching is limited to static assets and simple cache-control rules. Edge compute platforms let you write actual caching logic — personalized responses, conditional caching, and smart invalidation — running in hundreds of locations worldwide.

A Basic Edge Cache-Aside Pattern

export default {
  async fetch(request, env) {
    const cacheKey = new Request(request.url, request);
    const cache = caches.default;

    let response = await cache.match(cacheKey);
    if (response) return response;

    response = await fetch(request);
    response = new Response(response.body, response);
    response.headers.set('Cache-Control', 'max-age=300');

    ctx.waitUntil(cache.put(cacheKey, response.clone()));
    return response;
  },
};

Using KV for Structured Edge Data

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const productId = url.pathname.split('/').pop();

    const cached = await env.PRODUCT_CACHE.get(productId, { type: 'json' });
    if (cached) return Response.json(cached);

    const product = await fetchProductFromOrigin(productId);
    await env.PRODUCT_CACHE.put(productId, JSON.stringify(product), {
      expirationTtl: 3600,
    });

    return Response.json(product);
  },
};

Smart Invalidation on Content Updates

// Origin webhook triggered when a product is updated
async function handleProductUpdate(request, env) {
  const { productId } = await request.json();
  await env.PRODUCT_CACHE.delete(productId);

  // Also purge the CDN-level cache for the rendered page
  await fetch('https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache', {
    method: 'POST',
    headers: { Authorization: `Bearer ${env.CF_API_TOKEN}` },
    body: JSON.stringify({ files: [`https://example.com/products/${productId}`] }),
  });
}

Stale-While-Revalidate at the Edge

async function staleWhileRevalidate(cacheKey, cache, ctx, fetchFresh) {
  const cached = await cache.match(cacheKey);

  if (cached) {
    ctx.waitUntil(
      fetchFresh().then(fresh => cache.put(cacheKey, fresh.clone()))
    );
    return cached;
  }

  const fresh = await fetchFresh();
  ctx.waitUntil(cache.put(cacheKey, fresh.clone()));
  return fresh;
}

This serves a slightly stale response immediately while refreshing the cache in the background — users never wait for a cache miss on already-cached content.

Handling Personalized Content at the Edge

Vary your cache key by the dimensions that actually affect the response (locale, currency, auth status) rather than caching per-user, which defeats the purpose of edge caching:

const cacheKey = new Request(
  `${request.url}?locale=${locale}&currency=${currency}`,
  request
);

Conclusion

Edge caching earns its complexity when you need caching logic beyond what static CDN rules can express — personalization-aware keys, webhook-driven invalidation, and stale-while-revalidate patterns that keep responses both fast and reasonably fresh.