CDN and Edge Computing Patterns#

A CDN (Content Delivery Network) caches content at edge locations close to users, reducing latency and offloading traffic from origin servers. Edge computing extends this by running custom code at those edge locations, enabling request transformation, authentication, A/B testing, and dynamic content generation without round-tripping to an origin server.

CDN Cache Fundamentals#

Cache-Control Headers#

The origin server controls CDN caching behavior through HTTP headers. Getting these right is the single most impactful CDN optimization.

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

Key directives:

  • public: The response can be cached by CDNs (not just the browser).
  • private: Only the browser can cache this response (user-specific content).
  • max-age=N: Cache for N seconds. 31536000 = 1 year.
  • s-maxage=N: CDN-specific max-age that overrides max-age for shared caches.
  • immutable: The resource will never change at this URL. Browsers skip revalidation even on hard refresh.
  • no-cache: Cache the response but revalidate with the origin before serving (using ETag or Last-Modified).
  • no-store: Do not cache at all. Use for sensitive data (account pages, payment info).
  • stale-while-revalidate=N: Serve stale content for N seconds while fetching a fresh copy in the background.

Recommended patterns:

# Static assets with content hashes (app.a1b2c3.js)
Cache-Control: public, max-age=31536000, immutable

# HTML pages
Cache-Control: public, max-age=0, must-revalidate
# (Lets CDN cache with instant invalidation via purge)

# API responses (cacheable)
Cache-Control: public, s-maxage=60, stale-while-revalidate=300

# API responses (personalized)
Cache-Control: private, no-cache

# Sensitive data
Cache-Control: no-store

Cache Keys#

The cache key determines what counts as the “same” request. By default, CDNs cache based on the full URL including query parameters. This means ?utm_source=twitter creates a separate cache entry from ?utm_source=google.

Normalize cache keys by stripping irrelevant query parameters:

// Cloudflare Workers: normalize cache key
addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  // Remove marketing parameters from cache key
  ['utm_source', 'utm_medium', 'utm_campaign', 'fbclid'].forEach(
    param => url.searchParams.delete(param)
  );
  const cacheKey = new Request(url.toString(), event.request);
  event.respondWith(
    caches.default.match(cacheKey).then(cached =>
      cached || fetch(event.request).then(response => {
        event.waitUntil(caches.default.put(cacheKey, response.clone()));
        return response;
      })
    )
  );
});

Cache Invalidation Strategies#

Cache invalidation is genuinely hard. These are the practical approaches ordered by reliability.

Content-Addressed URLs (Best)#

Embed a hash in the filename: app.a1b2c3d4.js. When the content changes, the filename changes, and the old cached version is never served again. This is why build tools output hashed filenames. Combine with immutable and a long max-age.

This approach requires that the HTML file referencing these assets is either not cached or is invalidated on every deploy.

Purge on Deploy#

Purge the CDN cache as part of your deployment pipeline. Every major CDN provides a purge API.

# Cloudflare: purge everything
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
  -H "Authorization: Bearer ${CF_TOKEN}" \
  -d '{"purge_everything":true}'

# Cloudflare: purge specific URLs
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
  -H "Authorization: Bearer ${CF_TOKEN}" \
  -d '{"files":["https://example.com/index.html","https://example.com/api/config"]}'

# CloudFront: create invalidation
aws cloudfront create-invalidation \
  --distribution-id ${DIST_ID} \
  --paths "/index.html" "/api/*"

Warning: CloudFront invalidations are not instant (typically 5-15 minutes) and you get 1,000 free path invalidations per month. After that, $0.005 per path. Cloudflare purges propagate in under 30 seconds globally.

Surrogate Keys (Tag-Based Purging)#

Assign tags to cached responses and purge by tag. When a blog post is updated, purge all responses tagged with that post’s ID – including the post page, the homepage listing, the RSS feed, and any API responses that include it.

# Origin response header
Surrogate-Key: post-123 homepage blog-feed

# Purge all content tagged with post-123
curl -X POST "https://api.fastly.com/service/${SERVICE_ID}/purge/post-123" \
  -H "Fastly-Key: ${FASTLY_TOKEN}"

Fastly has the best surrogate key support. Cloudflare supports tag-based purging via Cache Tags on Enterprise plans.

Stale-While-Revalidate#

Not strictly invalidation but a practical complement. The CDN serves stale content immediately while fetching a fresh copy from the origin in the background. Users always get a fast response, and the cache updates within seconds.

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

This means: serve the cached version for 60 seconds. After 60 seconds, continue serving the cached version but fetch a fresh copy in the background. After 86,400 seconds (1 day) of staleness, stop serving the stale version and wait for a fresh response.

Origin Shielding#

Without origin shielding, every CDN edge location that gets a cache miss contacts your origin independently. If you have 200 edge locations and deploy a cache purge, your origin can get hit with 200 simultaneous requests for the same resource.

Origin shielding adds an intermediate cache layer between edge locations and your origin. Cache misses from edge locations go to the shield first. If the shield has the content, it serves it without hitting the origin. If not, only the shield contacts the origin.

User -> Edge (miss) -> Shield (miss) -> Origin
User -> Edge (miss) -> Shield (hit)  -> returns cached
User -> Edge (hit)  -> returns cached

CloudFront: Enable Origin Shield in distribution settings. Choose a shield region close to your origin.

Cloudflare: Argo Tiered Cache provides this automatically. Enable it in the dashboard or via API.

Fastly: Configure a shielding POP in your service settings.

Edge Function Platforms#

Cloudflare Workers#

Workers run on Cloudflare’s network across 300+ locations. They use the V8 JavaScript engine (not Node.js), which means no fs, no net, but the Web Standards APIs (fetch, Request, Response, crypto, streams) are available.

// wrangler.toml
// name = "my-worker"
// main = "src/index.js"

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // Route API requests to origin
    if (url.pathname.startsWith('/api/')) {
      return fetch(request);
    }

    // Serve static content from R2
    if (url.pathname.startsWith('/assets/')) {
      const object = await env.ASSETS.get(url.pathname.slice(8));
      if (!object) return new Response('Not found', { status: 404 });
      return new Response(object.body, {
        headers: { 'Cache-Control': 'public, max-age=31536000, immutable' },
      });
    }

    // Default: proxy to origin with custom header
    const modifiedRequest = new Request(request);
    modifiedRequest.headers.set('X-Edge-Location', request.cf?.colo || 'unknown');
    return fetch(modifiedRequest);
  },
};

Limits: 10ms CPU time (free), 30ms (paid). 128MB memory. No long-running processes.

Lambda@Edge and CloudFront Functions#

AWS offers two levels of edge compute.

CloudFront Functions run on every request (viewer-facing), support only JavaScript, and are limited to 10KB code size and 1ms execution time. Use for simple request/response manipulation: header modification, URL rewrites, redirects.

// CloudFront Function: URL rewrite
function handler(event) {
  var request = event.request;
  // Add index.html to directory requests
  if (request.uri.endsWith('/')) {
    request.uri += 'index.html';
  }
  return request;
}

Lambda@Edge runs on regional edge caches (not every POP), supports Node.js and Python, and can run for up to 30 seconds (origin-facing) or 5 seconds (viewer-facing). Use for dynamic content generation, authentication, and A/B testing.

Vercel Edge Functions#

Vercel Edge Functions run on Cloudflare’s network and use the same V8 runtime. They integrate tightly with Next.js middleware.

// middleware.ts (Next.js)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Geolocation-based redirect
  const country = request.geo?.country || 'US';
  if (country === 'DE' && !request.nextUrl.pathname.startsWith('/de')) {
    return NextResponse.redirect(new URL('/de' + request.nextUrl.pathname, request.url));
  }
  return NextResponse.next();
}

A/B Testing at the Edge#

Running A/B tests at the edge eliminates the flash of original content that client-side A/B testing causes. The edge assigns users to variants before the page is rendered.

// Cloudflare Worker: A/B test with sticky assignment
export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname !== '/pricing') return fetch(request);

    // Check for existing assignment
    const cookie = request.headers.get('Cookie') || '';
    let variant = cookie.match(/ab-pricing=(\w+)/)?.[1];

    // Assign new visitors randomly
    if (!variant) {
      variant = Math.random() < 0.5 ? 'control' : 'variant-b';
    }

    // Fetch the appropriate version
    url.pathname = variant === 'variant-b' ? '/pricing-b' : '/pricing';
    const response = await fetch(new Request(url, request));
    const newResponse = new Response(response.body, response);

    // Set sticky cookie (30 days)
    newResponse.headers.append(
      'Set-Cookie',
      `ab-pricing=${variant}; Path=/; Max-Age=2592000; SameSite=Lax`
    );

    return newResponse;
  },
};

Geolocation Routing#

CDNs provide geolocation data on every request. Use this for language selection, compliance (blocking restricted regions), and routing to regional backends.

// Cloudflare Worker: geolocation routing
export default {
  async fetch(request) {
    const country = request.cf?.country || 'US';
    const continent = request.cf?.continent || 'NA';

    // Route to regional API backends
    const backends = {
      NA: 'https://us-api.example.com',
      EU: 'https://eu-api.example.com',
      AS: 'https://ap-api.example.com',
    };
    const backend = backends[continent] || backends.NA;

    const url = new URL(request.url);
    url.hostname = new URL(backend).hostname;

    const response = await fetch(new Request(url, request));
    const newResponse = new Response(response.body, response);
    newResponse.headers.set('X-Served-By', continent);
    return newResponse;
  },
};

WAF Integration#

Modern CDNs bundle WAF (Web Application Firewall) capabilities. Configure WAF rules before traffic reaches your edge functions or origin.

Cloudflare WAF managed rules cover OWASP Top 10 automatically. Add custom rules for application-specific protection:

# Cloudflare custom WAF rule (wirefilter syntax)
# Block requests with SQL injection patterns in query params
(http.request.uri.query contains "UNION SELECT") or
(http.request.uri.query contains "1=1") or
(http.request.uri.query contains "DROP TABLE")

CloudFront + AWS WAF uses Web ACLs with rule groups:

# Terraform: AWS WAF with CloudFront
resource "aws_wafv2_web_acl" "cdn_waf" {
  name  = "cdn-protection"
  scope = "CLOUDFRONT"

  default_action { allow {} }

  rule {
    name     = "aws-managed-common"
    priority = 1
    override_action { none {} }
    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"
      }
    }
    visibility_config {
      sampled_requests_enabled   = true
      cloudwatch_metrics_enabled = true
      metric_name                = "CommonRules"
    }
  }

  visibility_config {
    sampled_requests_enabled   = true
    cloudwatch_metrics_enabled = true
    metric_name                = "CDNWaf"
  }
}

Rate limiting at the WAF layer protects against DDoS and brute-force attacks before they reach your application. Cloudflare rate limiting rules and AWS WAF rate-based rules both operate at the edge, dropping excessive traffic before it consumes origin resources.

Practical Checklist for Agents#

When configuring CDN and edge computing for a project:

  1. Set Cache-Control headers at the origin. Do not rely on CDN dashboard overrides as the primary mechanism.
  2. Use content-hashed URLs for static assets with immutable and long max-age.
  3. Implement cache purge in the deployment pipeline for non-hashed resources.
  4. Enable origin shielding to protect origins during cache purges and traffic spikes.
  5. Use edge functions for request routing, not for heavy computation. If the function needs more than 10ms of CPU, it belongs on the origin.
  6. Always set sticky cookies for A/B tests to prevent users from switching variants between page loads.
  7. Enable WAF managed rules as a baseline. Add custom rules for application-specific threats.
  8. Monitor cache hit ratio. A healthy CDN should serve 85-95% of requests from cache. Below 70% indicates a cache key or header misconfiguration.