Skip to main content
โšก Calmops

CDN and Edge Computing: Global Content Delivery and Cache Management

Table of Contents

Content Delivery Networks (CDNs) and edge computing have become essential infrastructure components for modern web applications. Whether you’re building a SaaS platform, e-commerce site, or mobile app backend, understanding how to leverage these technologies can dramatically improve user experience, reduce latency, and increase application reliability.

Today’s users expect sub-100ms response times regardless of geography. A single origin server, no matter how powerful, cannot meet this expectation when users are distributed globally. CDNs and edge computing represent the modern solution to this challenge, enabling companies to deliver content and compute at the speed of light.

This comprehensive guide explores how CDNs work, introduces edge computing concepts, covers practical use cases, and explains cache management strategies. By the end, you’ll understand when and how to implement these technologies effectively.


What is a CDN? The Foundation of Global Content Delivery

The Problem CDNs Solve

Imagine a user in Tokyo trying to access your website hosted on a single server in Virginia. The request must travel thousands of miles across multiple networks, introducing latency at every hop. This is the problem CDNs solve.

Traditional architecture:

User in Tokyo
    โ†“ (long distance, high latency)
    โ†“ (multiple network hops)
    โ†“ (single origin server in Virginia)
    โ†“ Response travels back same long path
User receives response after 500-1000ms

How CDNs Work

A CDN is a geographically distributed network of servers (called Points of Presence or PoPs) that cache and serve your content from locations near your users.

CDN architecture:

User in Tokyo
    โ†“
    โ†“ (requests go to nearest PoP)
    โ†“
Tokyo PoP (has cached content)
    โ†“
User receives response in ~50-100ms

Meanwhile, if Tokyo PoP doesn't have content:
Tokyo PoP
    โ†“ (requests from origin)
    โ†“
Origin Server (Virginia)
    โ†“ (response cached in Tokyo PoP)
Tokyo PoP now has content for future requests

Key Concepts

Points of Presence (PoPs): Geographic locations where CDN servers are deployed. A large CDN might have 100+ PoPs worldwide.

Edge Servers: Physical servers at each PoP that cache and serve content.

Origin Server: Your original web server where content is created and initially stored.

Cache Hit: When a requested resource is found in an edge server’s cache.

Cache Miss: When a requested resource is not in cache and must be fetched from the origin.

TTL (Time-To-Live): How long content remains cached before being considered stale.

The CDN Request Flow

1. User requests content (image, HTML, API response)
   โ†“
2. DNS lookup resolves to nearest CDN PoP
   โ†“
3. User connects to edge server
   โ†“
4. Edge server checks cache
   โ”œโ”€ Cache Hit โ†’ Serve cached content (fast, ~100ms)
   โ””โ”€ Cache Miss โ†’ Fetch from origin (slower, ~500-1000ms)
   โ†“
5. If cache miss, edge server fetches from origin
   โ†“
6. Response cached at edge server with TTL
   โ†“
7. Next request for same content hits cache

Types of Content CDNs Can Serve

Static Content (ideal for CDN):

  • Images, videos, PDFs
  • JavaScript, CSS files
  • Fonts, icons
  • Static HTML pages

Dynamic Content (increasingly possible):

  • HTML pages with personalization
  • API responses
  • Database query results
  • Real-time data streams

NOT suitable for CDN:

  • Highly sensitive data (medical records, financial data)
  • Content that changes per user/request
  • Very large video files (though edge caching helps)

Understanding Traditional CDN Limitations

The Traditional CDN Model

Most CDNs (Akamai, Cloudflare, Fastly) traditionally work like this:

  1. Your origin server handles all dynamic requests
  2. CDN caches static content close to users
  3. For dynamic content, requests still go to origin
  4. Origin processes, CDN caches result (if cacheable)

Problem: Dynamic content still requires origin round trip.

Example scenario:

User in Australia requests:
GET /api/products?category=electronics

CDN checks: Not cached (dynamic query)
CDN forwards to origin in Virginia
Origin queries database, generates response (200-500ms)
CDN caches response for TTL
User in Australia finally receives response (700-1200ms)

Next user in Australia with same query:
Hits cache immediately (100ms)

Different user asks:
GET /api/products?category=clothing

CDN doesn't have this query cached
Same 700-1200ms delay

The Origin Bottleneck

If your origin can only handle 1,000 requests/second but you have 10,000 users, the origin becomes a bottleneck. CDN can cache frequently requested content, but it can’t help with:

  • Cache misses (requests not in cache)
  • Dynamic personalized content
  • Real-time data requiring computation
  • Authentication and authorization logic

This is where edge computing comes in.


Edge Computing: Bringing Computation to the Edge

The Edge Computing Model

Edge computing extends traditional CDNs by allowing you to run code at edge servers. Instead of just caching static content, you can execute functions on edge servers near users.

Traditional CDN:
Cache Hit โ†’ Serve from edge (fast)
Cache Miss โ†’ Go to origin (slow)

Edge Computing:
Cache Hit โ†’ Serve from edge (fast)
Cache Miss + Can run at edge โ†’ Execute function at edge (fast)
Cache Miss + Can't run at edge โ†’ Go to origin (slow)

Where Edge Functions Execute

Edge functions run on servers distributed globally:

Edge Servers (execute code):
โ”œโ”€ Process requests before reaching origin
โ”œโ”€ Modify requests (add headers, authentication)
โ”œโ”€ Transform responses (compress, personalize)
โ”œโ”€ Handle authentication/authorization
โ”œโ”€ A/B testing logic
โ”œโ”€ Geolocation-based routing
โ””โ”€ Much closer to users (low latency)

vs

Origin Server (traditional):
โ””โ”€ Far from most users (high latency)

Edge Computing Platforms

Cloudflare Workers:

  • Runs on Cloudflare’s global network (250+ cities)
  • JavaScript/WebAssembly
  • Extremely fast (sub-millisecond cold start)

Vercel Edge Functions:

  • Runs on Vercel’s edge network (35+ regions)
  • Built into Next.js
  • JavaScript/TypeScript

AWS Lambda@Edge:

  • Runs on CloudFront (AWS CDN)
  • JavaScript/Node.js, Python
  • Integrated with AWS ecosystem

Fastly Compute:

  • Runs on Fastly’s network
  • Multiple language support
  • Lower latency than Lambda@Edge

Common Edge Function Use Cases

1. Authentication & Authorization

Check user authentication at the edge before requests reach origin.

// Cloudflare Worker example
export default {
  async fetch(request, env) {
    // Extract JWT from cookie
    const token = request.headers.get('cookie')?.match(/token=([^;]+)/)?.[1];
    
    if (!token) {
      return new Response('Unauthorized', { status: 401 });
    }
    
    // Verify JWT at edge (no origin call needed)
    try {
      const decoded = await verifyJWT(token, env.JWT_SECRET);
      
      // Add user info to request for origin
      const newRequest = new Request(request, {
        headers: {
          'X-User-ID': decoded.userId,
          'X-User-Role': decoded.role
        }
      });
      
      return fetch(newRequest); // Now forward to origin
    } catch (err) {
      return new Response('Invalid token', { status: 401 });
    }
  }
};

Benefits:

  • Authentication happens at edge (low latency)
  • Reduces load on origin
  • Blocks unauthorized requests before they reach origin
  • Single verification logic across all edge locations

2. A/B Testing

Route users to different versions without origin involvement.

// Vercel Edge Function example
import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const userAgent = request.headers.get('user-agent');
  const path = request.nextUrl.pathname;
  
  // A/B test: New checkout flow for 10% of users
  if (path.startsWith('/checkout')) {
    const variant = Math.random() > 0.9 ? 'new' : 'old';
    
    if (variant === 'new') {
      // Route to new checkout component
      const response = NextResponse.rewrite(
        new URL('/checkout-v2', request.url)
      );
      response.headers.set('X-AB-Test', 'checkout-v2');
      return response;
    }
  }
  
  // Consistent variant per user
  const cookie = request.cookies.get('ab-test');
  if (cookie) {
    const response = NextResponse.next();
    response.headers.set('X-AB-Test', cookie.value);
    return response;
  }
}

Benefits:

  • No origin knowledge of test variants
  • Consistent experience per user
  • Real-time analytics (read from cookies/headers)
  • Can switch variants without deploying origin

3. Personalization

Customize content based on user attributes without origin computation.

// Cloudflare Worker example
export default {
  async fetch(request) {
    const country = request.headers.get('cf-ipcountry');
    const deviceType = request.headers.get('sec-ch-ua-mobile') === '?1' 
      ? 'mobile' 
      : 'desktop';
    
    // Personalized homepage path
    const personalizedPath = `/homepage-${country}-${deviceType}`;
    
    // Try personalized cache first
    const personalizedResponse = await caches.default.match(personalizedPath);
    if (personalizedResponse) {
      return personalizedResponse;
    }
    
    // Fall back to generic homepage
    const response = await fetch(request);
    return response;
  }
};

Benefits:

  • Low-latency personalization
  • No computation on origin
  • Cache different versions per geography/device
  • Instant response to user

4. Request Transformation & Validation

Modify, validate, or reject requests at edge.

// Verify request format before origin
export default {
  async fetch(request) {
    // Only allow specific methods
    if (!['GET', 'POST', 'PUT', 'DELETE'].includes(request.method)) {
      return new Response('Method not allowed', { status: 405 });
    }
    
    // Validate content-type for POST
    if (request.method === 'POST') {
      const contentType = request.headers.get('content-type');
      if (!contentType?.includes('application/json')) {
        return new Response('Invalid content type', { status: 400 });
      }
      
      // Parse and validate body
      try {
        const body = await request.json();
        
        // Reject if too large
        if (JSON.stringify(body).length > 10000) {
          return new Response('Request too large', { status: 413 });
        }
        
        // Validate required fields
        if (!body.email || !body.password) {
          return new Response('Missing required fields', { status: 400 });
        }
      } catch (err) {
        return new Response('Invalid JSON', { status: 400 });
      }
    }
    
    // Forward valid requests
    return fetch(request);
  }
};

Benefits:

  • Invalid requests rejected at edge (no origin load)
  • Consistent validation across all edge locations
  • Quick feedback to client
  • DDoS mitigation (reject bad requests immediately)

5. Response Transformation

Modify responses at edge without origin involvement.

// Compress and optimize responses
export default {
  async fetch(request) {
    const response = await fetch(request);
    
    // Clone response to modify
    const newResponse = response.clone();
    
    // Add security headers
    const headers = new Headers(newResponse.headers);
    headers.set('X-Content-Type-Options', 'nosniff');
    headers.set('X-Frame-Options', 'DENY');
    headers.set('Strict-Transport-Security', 'max-age=31536000');
    
    // Cache longer for static assets
    if (request.url.endsWith('.js') || request.url.endsWith('.css')) {
      headers.set('Cache-Control', 'public, max-age=31536000');
    }
    
    return new Response(newResponse.body, {
      status: newResponse.status,
      statusText: newResponse.statusText,
      headers: headers
    });
  }
};

Benefits:

  • Consistent headers across requests
  • No origin overhead
  • Fast response transformation
  • Security improvements

6. Geolocation-Based Routing

Route requests to different origins based on user location.

// Route users to nearest regional server
export default {
  async fetch(request, env) {
    const country = request.headers.get('cf-ipcountry');
    
    const regions = {
      'US': 'https://us-api.example.com',
      'EU': 'https://eu-api.example.com',
      'APAC': 'https://apac-api.example.com'
    };
    
    let origin = regions['US']; // default
    
    if (['DE', 'FR', 'GB', 'IT', 'ES'].includes(country)) {
      origin = regions['EU'];
    } else if (['JP', 'AU', 'SG', 'CN', 'IN'].includes(country)) {
      origin = regions['APAC'];
    }
    
    const url = new URL(request.url);
    url.hostname = new URL(origin).hostname;
    
    return fetch(new Request(url, request));
  }
};

Benefits:

  • Lower latency (users hit nearest server)
  • Regulatory compliance (data residency)
  • Load balancing across regions
  • No origin routing logic needed

7. Rate Limiting & DDoS Protection

Limit requests per IP or user at edge.

// Rate limiting at edge
const rateLimitMap = new Map();

export default {
  async fetch(request) {
    const clientIP = request.headers.get('cf-connecting-ip');
    const now = Date.now();
    
    const limit = rateLimitMap.get(clientIP) || { count: 0, resetAt: now + 60000 };
    
    // Reset if window expired
    if (now > limit.resetAt) {
      limit.count = 0;
      limit.resetAt = now + 60000;
    }
    
    limit.count++;
    rateLimitMap.set(clientIP, limit);
    
    // Allow 100 requests per minute per IP
    if (limit.count > 100) {
      return new Response('Too many requests', { 
        status: 429,
        headers: { 'Retry-After': '60' }
      });
    }
    
    return fetch(request);
  }
};

Benefits:

  • DDoS protection at edge
  • No origin overload
  • Per-user rate limiting
  • Instant response to attackers

Caching Fundamentals

Cache Headers and HTTP Caching

HTTP headers control how content is cached:

// Example responses with different cache strategies

// Cache for 1 hour, cacheable by CDN and browsers
GET /api/articles
Cache-Control: public, max-age=3600

// Don't cache, always fetch fresh
GET /api/user/profile
Cache-Control: no-cache, no-store

// Cache in browser only, not CDN
GET /static/user-avatar.jpg
Cache-Control: private, max-age=86400

// Cache with revalidation
GET /api/products
Cache-Control: public, max-age=300
ETag: "33a64df551โ€ฆ"
// Browser asks: Is this still fresh?
// Server: Yes (304 Not Modified) or sends new version

TTL (Time-To-Live) Strategy

Short TTL (5-15 minutes):

  • Frequently changing content (news feeds, prices)
  • User-specific data (profiles, preferences)
  • API responses affected by real-time factors

Medium TTL (1-4 hours):

  • Semi-static content (product lists, categories)
  • Content updated daily
  • Good balance between freshness and caching benefit

Long TTL (1-30 days):

  • Static assets with versioning (app-abc123.js)
  • Images, videos, documents
  • Content that rarely changes

No Cache / Cache-Control: no-store:

  • Sensitive data (tokens, personal info)
  • Dynamic per-user content
  • Content requiring immediate freshness

Cache Invalidation Patterns

Purge on Deploy:

// When deploying new version, purge all cached content
const purgeAll = async () => {
  const response = await fetch(
    `https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache`,
    {
      method: 'POST',
      headers: {
        'X-Auth-Email': CF_EMAIL,
        'X-Auth-Key': CF_API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ purge_everything: true })
    }
  );
  return response.json();
};

Selective Purge (Recommended):

// Only purge affected paths
const selectivePurge = async (paths) => {
  const response = await fetch(
    `https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache`,
    {
      method: 'POST',
      headers: {
        'X-Auth-Email': CF_EMAIL,
        'X-Auth-Key': CF_API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ 
        files: paths.map(p => `https://example.com${p}`)
      })
    }
  );
  return response.json();
};

// Usage: Purge specific paths when content changes
selectivePurge([
  '/api/products',
  '/api/articles',
  '/homepage'
]);

Versioning-Based Cache Busting:

// Static assets with hash in filename
// app-a1b2c3d4.js - includes hash of content
// When content changes, hash changes, old version stays cached forever
// New version gets new filename

// In HTML
<script src="/js/app-a1b2c3d4.js"></script> <!-- cached forever -->

// After deploy with new code
<script src="/js/app-x9y8z7w6.js"></script> <!-- new version -->

// Benefits:
// - Browsers cache old version indefinitely
// - New version gets separate cache entry
// - No "purge everything" needed
// - Instant updates for users

Tag-Based Purge:

// Add cache tags to responses
export default {
  async fetch(request) {
    const response = await fetch(request);
    
    // Tag response with logical groups
    response.headers.set('Cache-Tag', 'products,homepage,articles');
    
    return response;
  }
};

// Purge all responses tagged with 'products'
const tagPurge = async (tag) => {
  const response = await fetch(
    `https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache`,
    {
      method: 'POST',
      headers: {
        'X-Auth-Email': CF_EMAIL,
        'X-Auth-Key': CF_API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ 
        tags: [tag]
      })
    }
  );
  return response.json();
};

// When products update, purge all product-tagged content
await tagPurge('products');

Cache Purging Strategies

When to Purge Cache

Must purge:

  • Security fixes deployed
  • Critical bug fixes
  • Data corruption fixes
  • Breaking changes

Should purge:

  • Feature releases
  • Content updates
  • Design changes
  • New functionality

Don’t need to purge (use TTL):

  • Minor updates
  • Non-user-facing changes
  • Gradual rollouts
  • A/B tests

Full Purge vs Selective Purge

Full Purge (Purge Everything):

// Removes ALL cached content across entire CDN
// Use sparingly - expensive operation

const purgeAll = async () => {
  const response = await fetch(
    `https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache`,
    {
      method: 'POST',
      headers: { /* credentials */ },
      body: JSON.stringify({ purge_everything: true })
    }
  );
  return response.json();
};

// Impact:
// - All cached content removed immediately
// - Next 100,000 requests all hit origin
// - Origin receives traffic spike
// - Slower responses for users (cache misses)
// - High CDN API cost

Selective Purge (Targeted):

// Only remove affected content
// Much better for performance

const selectivePurge = async (paths) => {
  const response = await fetch(
    `https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache`,
    {
      method: 'POST',
      headers: { /* credentials */ },
      body: JSON.stringify({ 
        files: paths.map(p => `https://example.com${p}`)
      })
    }
  );
  return response.json();
};

// Example: Blog post update
selectivePurge([
  '/blog/my-post',
  '/api/blog-posts',
  '/api/recent-posts',
  '/sitemap.xml'
]);

// Impact:
// - Only affected content removed
// - Other cached content still served
// - Users experience normal performance
// - Origin handles only changed content

Cache Purge Implementation

Automated Purge on Content Update:

// Database update triggers cache purge
const updateProduct = async (productId, data) => {
  // Update database
  const product = await db.products.update(productId, data);
  
  // Purge related caches
  const paths = [
    `/api/products/${productId}`,
    `/api/products`,
    `/product/${productId}`,
    `/api/categories/${product.categoryId}`,
    '/sitemap.xml'
  ];
  
  await selectivePurge(paths);
  
  return product;
};

Manual Purge with Dashboard:

Most CDNs provide dashboard UI:

Cloudflare Dashboard:
1. Login to Cloudflare
2. Select domain
3. Go to "Caching" tab
4. Click "Purge Cache"
5. Select: Purge Everything or Purge by URL
6. Enter URLs (one per line)
7. Confirm

Via CLI:
wrangler pages project list
wrangler pages deployment list

Scheduled Purge:

// Purge cache every night at 2 AM
// Ensures fresh content for morning traffic

import cron from 'node-cron';

// Every day at 2 AM
cron.schedule('0 2 * * *', async () => {
  console.log('Running scheduled cache purge...');
  
  const paths = [
    '/api/homepage',
    '/api/featured-products',
    '/latest-news'
  ];
  
  await selectivePurge(paths);
  console.log('Cache purge complete');
});

Monitoring Cache Performance

// Track cache hit ratio
const monitorCache = async () => {
  // Cloudflare Analytics
  const response = await fetch(
    `https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/analytics/dashboard`,
    {
      headers: { /* credentials */ }
    }
  );
  
  const data = await response.json();
  
  return {
    cacheHitRatio: data.result.timeseries[0].cache_hit_ratio,
    totalRequests: data.result.timeseries[0].requests.total,
    cachedRequests: data.result.timeseries[0].requests.cached
  };
};

// Dashboard displays:
// - Cache hit ratio (target: 80%+)
// - Cache miss reasons
// - Top purged URLs
// - Origin latency
// - Edge latency

CDN vs Edge Computing: When to Use Each

Use Traditional CDN

  • Static content (images, videos, CSS, JS)
  • Simple caching strategies
  • Serving same content to all users
  • Budget-conscious projects
  • Low compute requirements

Example:

Blog website
โ”œโ”€ Static HTML pages โ†’ CDN
โ”œโ”€ Images โ†’ CDN
โ”œโ”€ CSS/JS โ†’ CDN with long TTL
โ””โ”€ Comments โ†’ Cached API response

Use Edge Computing

  • Authentication before reaching origin
  • Personalization based on user attributes
  • A/B testing and feature flags
  • Request validation and transformation
  • Real-time personalization
  • Geolocation-based routing
  • Security headers and policies
  • Rate limiting and DDoS protection

Example:

E-commerce platform
โ”œโ”€ Product images โ†’ CDN
โ”œโ”€ Product listings โ†’ CDN (short TTL)
โ”œโ”€ Shopping cart โ†’ Origin (user-specific)
โ”œโ”€ Checkout โ†’ Edge function (auth + validation)
โ””โ”€ Admin dashboard โ†’ Edge function (auth only)
User Request
    โ†“
Edge Function (authentication, validation, routing)
    โ”œโ”€ Request valid + can serve from cache?
    โ”‚   โ†“
    โ”‚   Cache (static content, API responses)
    โ”‚   โ†“
    โ”‚   Return to user (fast)
    โ”‚
    โ””โ”€ Request valid + cache miss?
        โ†“
        Origin Server
        โ†“
        Cache result if appropriate
        โ†“
        Return to user

This hybrid model gives you:

  • Instant authentication
  • Smart caching
  • Fast static content delivery
  • Origin protection
  • Flexible routing

Performance Comparison

Real-World Latency Numbers

Single origin server (Virginia):

User location:        Response time:
San Francisco         50-100ms
New York              10-50ms
London                150-250ms
Tokyo                 500-800ms
Sydney                600-900ms
Average:              ~300-400ms

With CDN (without edge functions):

User location:        Response time:
San Francisco         20-40ms (nearby PoP)
New York              5-20ms (nearby PoP)
London                30-60ms (nearby PoP)
Tokyo                 50-80ms (nearby PoP)
Sydney                40-70ms (nearby PoP)
Average:              ~40-70ms (85% faster)

With CDN + Edge Functions:

User location:        Response time:
San Francisco         5-15ms (edge function)
New York              2-10ms (edge function)
London                10-20ms (edge function)
Tokyo                 10-20ms (edge function)
Sydney                10-25ms (edge function)
Average:              ~10-20ms (95% faster than origin)

Cost Considerations

Traditional CDN:

  • Per GB transferred: $0.08-$0.50
  • Bandwidth savings: 30-50%
  • Good ROI for bandwidth-heavy content

Edge Functions:

  • Per million requests: $0.50-$2.00
  • Execution cost: minimal (<$5-50/month for most)
  • Better ROI for dynamic content

Combined:

  • Lowest latency
  • Best user experience
  • Moderate cost (~$100-500/month for small sites)

Best Practices and Optimization Tips

Caching Best Practices

  1. Always set Cache-Control headers:
// Static assets - cache forever (use versioning)
app.use('/static', (req, res) => {
  res.set('Cache-Control', 'public, max-age=31536000, immutable');
  // ... serve files
});

// API responses - short TTL
app.get('/api/data', (req, res) => {
  res.set('Cache-Control', 'public, max-age=300');
  res.json(data);
});

// User-specific - don't cache
app.get('/api/profile', (req, res) => {
  res.set('Cache-Control', 'private, no-cache, no-store');
  res.json(userProfile);
});
  1. Use versioning for static assets:
// Build tool (webpack, esbuild) adds hash
build: {
  output: {
    filename: 'js/[name].[contenthash:8].js',
    assetFilename: 'assets/[name].[contenthash:8][ext]'
  }
}

// Results in:
// app.a1b2c3d4.js (cached forever)
// app.x9y8z7w6.js (new version after deploy)
// No manual purge needed
  1. Minimize cache misses:
// Pre-warm cache before deploy
const warmCache = async () => {
  const urls = [
    '/api/products',
    '/api/categories',
    '/api/featured',
    '/',
    '/about',
    '/contact'
  ];
  
  for (const url of urls) {
    await fetch(`https://example.com${url}`);
  }
};

// Run after deploy to populate edge caches

Edge Function Best Practices

  1. Keep functions small and fast:
// Good: Simple, fast logic
export default {
  async fetch(request) {
    if (request.method !== 'GET') {
      return new Response('Method not allowed', { status: 405 });
    }
    return fetch(request);
  }
};

// Avoid: Heavy computation
export default {
  async fetch(request) {
    // DON'T: Complex ML inference
    // DON'T: Long database queries
    // DON'T: Large file processing
  }
};
  1. Leverage request context headers:
// Cloudflare provides useful headers
export default {
  async fetch(request) {
    const headers = {
      country: request.headers.get('cf-ipcountry'),
      timezone: request.headers.get('cf-timezone'),
      colo: request.headers.get('cf-ray'),
      device: request.headers.get('sec-ch-ua-mobile'),
      asn: request.headers.get('cf-asn')
    };
    
    // Use for routing, personalization, analytics
    return fetch(request);
  }
};
  1. Cache edge function responses:
// Edge functions can also cache results
export default {
  async fetch(request) {
    // Check edge cache
    const cached = await caches.default.match(request);
    if (cached) return cached;
    
    // Process request
    const response = await fetch(request);
    
    // Cache response for 5 minutes
    response.headers.set('Cache-Control', 'public, max-age=300');
    caches.default.put(request, response.clone());
    
    return response;
  }
};

Troubleshooting Common Issues

Problem: Cache not updating after deploy

Solution: Use versioning for static assets or selective purge

Problem code (no cache busting)
<script src="/js/app.js"></script>

Solution: Add hash
<script src="/js/app.a1b2c3d4.js"></script>

After deploy with changes
<script src="/js/app.x9y8z7w6.js"></script>

Problem: Users seeing stale data

Solutions:

// 1. Reduce TTL
res.set('Cache-Control', 'public, max-age=60'); // 1 minute instead of 1 hour

// 2. Use ETag for conditional requests
res.set('ETag', `"${hash(content)}"`);

// 3. Add Vary header for content differences
res.set('Vary', 'Accept-Encoding, User-Agent');

// 4. Purge on data change
updateData(id, newData);
await selectivePurge([`/api/data/${id}`]);

Problem: High origin traffic after purge

Solutions:

// 1. Use gradual purge (don't purge everything at once)
const gradualPurge = async (paths) => {
  for (const path of paths) {
    await selectivePurge([path]);
    await new Promise(r => setTimeout(r, 100)); // 100ms between purges
  }
};

// 2. Pre-warm cache after purge
const purgeAndWarm = async (paths) => {
  await selectivePurge(paths);
  
  // Wait for purge to complete
  await new Promise(r => setTimeout(r, 5000));
  
  // Pre-warm cache
  for (const path of paths) {
    await fetch(`https://example.com${path}`);
  }
};

// 3. Use stale-while-revalidate
res.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=3600');
// Serves stale content while fetching fresh version


Advanced CDN Architecture Patterns

Multi-CDN Strategy

Many large organizations use multiple CDNs simultaneously for improved reliability and performance:

User Request
    โ†“
GeoDNS Router
    โ”œโ”€ North America โ†’ Cloudflare PoP
    โ”œโ”€ Europe โ†’ Fastly PoP
    โ”œโ”€ Asia-Pacific โ†’ AWS CloudFront
    โ””โ”€ South America โ†’ Akamai PoP

Benefits:
- Vendor independence (no single point of failure)
- Optimized routing per region
- Competitive pricing (play CDNs against each other)
- Best-of-breed features from each provider

Example GeoDNS configuration:
// Using route53 for multi-CDN routing
const route53Config = {
  'api.example.com': {
    'us-*': 'cloudflare.example.com',           // US users
    'eu-*': 'fastly.example.com',               // EU users
    'ap-*': 'aws.example.com',                  // Asia-Pacific
    'sa-*': 'akamai.example.com'                // South America
  }
};

Origin Shield Pattern

Origin Shield is an additional caching layer between edge PoPs and your origin server:

Without Origin Shield:
User 1 in New York โ†’ CDN PoP (cache miss)
                  โ†“
                Origin (100ms)
                  
User 2 in New York โ†’ CDN PoP (cache miss) 
                  โ†“
                Origin (100ms)

Result: Multiple cache misses, origin overloaded

With Origin Shield:
User 1 in New York โ†’ CDN PoP (cache miss)
                  โ†“
            Origin Shield (consolidates requests)
                  โ†“
                Origin (100ms)
                  
User 2 in New York โ†’ CDN PoP (cache miss)
                  โ†“
            Origin Shield (returns cached from previous request)

Result: Fewer origin requests, better cache hit ratio

When to use Origin Shield:

  • High traffic sites (100k+ requests/day)
  • Expensive origin computations
  • Protecting unprepared origin servers
  • Origin in unstable region

Stale-While-Revalidate Pattern

Modern cache header that improves perceived performance:

// Instead of:
res.set('Cache-Control', 'public, max-age=300');
// After 5 minutes, users get cache miss

// Use:
res.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=3600');
// After 5 minutes, serve stale content while fetching fresh
// After 1 hour total, force fresh fetch

// Timeline:
// 0-300s: Serve cached, up-to-date
// 300-3600s: Serve cached (stale), revalidate in background
// 3600s+: Force fresh fetch

// Benefits:
// - Users always get response immediately
// - Origin doesn't receive thundering herd
// - Content freshness within reasonable window
// - Significantly improved perceived performance

Real-World Performance Case Studies

E-Commerce Platform

Before CDN:

Metric                Value
Average latency       450ms
P95 latency          900ms
Origin requests/sec  5,000+
Cache hit ratio      N/A (no cache)
Peak server cost     $50K/month
User bounce rate     35%

After CDN + Edge:

Metric                Value
Average latency       80ms
P95 latency          150ms
Origin requests/sec  500
Cache hit ratio      92%
Peak server cost     $8K/month
User bounce rate     12%

Implementation:

  • Product images โ†’ CDN (long TTL + versioning)
  • Product listings โ†’ Edge cached (30m TTL + selective purge on inventory change)
  • Shopping cart โ†’ Origin (user-specific)
  • Checkout โ†’ Edge functions (validation + auth)
  • Admin โ†’ Edge function (auth-only)

SaaS Dashboard Application

Before Edge:

Dashboard loads with:
1. Auth check (50ms)
2. User profile fetch (100ms)
3. Dashboard data (200ms)
Total: 350ms

After Edge Functions:

1. Auth check at edge (5ms)
2. User profile cached at edge (10ms)
3. Dashboard data partial cache (50ms)
Total: 65ms

Changes:

  • Moved auth to edge function
  • Cache user profile at edge (30m TTL)
  • Implement selective purge when profile updates
  • Pre-render dashboard data at edge for common scenarios

API Optimization with CDN

Caching Strategies for APIs

REST API Caching:

// GET /api/products โ†’ Always cacheable
app.get('/api/products', (req, res) => {
  // Cache for 5 minutes
  res.set('Cache-Control', 'public, max-age=300');
  res.json(products);
});

// POST /api/products โ†’ Never cacheable
app.post('/api/products', (req, res) => {
  res.set('Cache-Control', 'no-cache, no-store');
  // Create product
});

// GET /api/products/:id โ†’ Cacheable but invalidate on update
app.get('/api/products/:id', (req, res) => {
  res.set('Cache-Control', 'public, max-age=3600');
  res.set('Cache-Tag', `product-${req.params.id}`);
  res.json(product);
});

GraphQL API Caching (More Complex):

// GraphQL queries are POST requests - not cached by default
// Solution 1: Use persistent queries (pre-defined queries)
app.get('/api/graphql/:queryId', (req, res) => {
  // Map queryId to pre-defined query
  const query = persistentQueries[req.params.queryId];
  
  // Execute and cache
  res.set('Cache-Control', 'public, max-age=300');
  res.json(executeQuery(query));
});

// Solution 2: Automatic persisted queries
app.post('/api/graphql', (req, res) => {
  const hash = hashQuery(req.body.query);
  
  // Check cache first
  const cached = await cache.get(hash);
  if (cached) {
    res.set('Cache-Control', 'public, max-age=300');
    return res.json(cached);
  }
  
  // Execute and cache
  const result = executeGraphQL(req.body);
  cache.set(hash, result, 300);
  res.json(result);
});

API Versioning for Cache Stability:

// Bad: API changes break caches
GET /api/products โ†’ Returns different schema over time

// Good: Version API, cache independently
GET /api/v1/products โ†’ Schema stable, cached forever
GET /api/v2/products โ†’ New schema, new cache

// In code:
app.get('/api/v1/products', (req, res) => {
  res.set('Cache-Control', 'public, max-age=31536000'); // 1 year
  res.set('ETag', '"v1-schema"');
  res.json(products);
});

app.get('/api/v2/products', (req, res) => {
  res.set('Cache-Control', 'public, max-age=600'); // 10 min
  res.set('ETag', '"v2-schema"');
  res.json(products);
});

Security Considerations with CDN/Edge

Security Best Practices

1. Sensitive Data Should Never Be Cached:

// โŒ Wrong - Caches sensitive data
app.get('/api/user/credit-card', (req, res) => {
  // This gets cached!
  res.json(user.creditCard);
});

// โœ… Correct - Explicitly prevent caching
app.get('/api/user/credit-card', (req, res) => {
  res.set('Cache-Control', 'private, no-cache, no-store');
  res.set('Pragma', 'no-cache');
  res.json(user.creditCard);
});

2. HTTPS/TLS Enforcement:

// Always use HTTPS for CDN connections
// CDN encrypts traffic from user to edge
// Also encrypt traffic from edge to origin

// Edge function forcing HTTPS:
export default {
  async fetch(request) {
    if (!request.url.startsWith('https://')) {
      const url = new URL(request.url);
      url.protocol = 'https:';
      return Response.redirect(url.toString(), 301);
    }
    return fetch(request);
  }
};

3. CORS and Security Headers at Edge:

// Set security headers at edge (fast, consistent)
export default {
  async fetch(request) {
    const response = await fetch(request);
    const headers = new Headers(response.headers);
    
    // Security headers
    headers.set('X-Content-Type-Options', 'nosniff');
    headers.set('X-Frame-Options', 'DENY');
    headers.set('X-XSS-Protection', '1; mode=block');
    headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
    headers.set('Content-Security-Policy', "default-src 'self'");
    
    // CORS
    headers.set('Access-Control-Allow-Origin', 'https://trusted.com');
    headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT');
    headers.set('Access-Control-Allow-Credentials', 'true');
    
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: headers
    });
  }
};

4. API Key Management:

// Don't store API keys in client-side cache
// Use edge functions as proxy

// Client code (safe - no key exposure)
fetch('/api-proxy/external-service', {
  method: 'GET'
});

// Edge function (has secret key)
export default {
  async fetch(request, env) {
    if (request.url.includes('/api-proxy/')) {
      const externalUrl = extractUrl(request.url);
      
      // Add API key at edge (never exposed to client)
      const newRequest = new Request(externalUrl, {
        headers: {
          'X-API-Key': env.EXTERNAL_API_KEY
        }
      });
      
      return fetch(newRequest);
    }
  }
};

5. Rate Limiting and DDoS at Edge:

// Protection before requests reach origin
export default {
  async fetch(request, env) {
    const clientIP = request.headers.get('cf-connecting-ip');
    const key = `rate-limit:${clientIP}`;
    
    // Get current count from cache
    const count = await env.CACHE.get(key) || 0;
    
    if (count > 1000) {
      return new Response('Too many requests', { 
        status: 429,
        headers: { 'Retry-After': '60' }
      });
    }
    
    // Increment counter
    await env.CACHE.put(key, count + 1, { expirationTtl: 60 });
    
    // Request is safe, forward to origin
    return fetch(request);
  }
};

Monitoring and Analytics

Key Metrics to Track

Performance Metrics:

// Monitor via CDN analytics dashboard
metrics = {
  cacheHitRatio: 85,        // Target: 80%+
  averageLatency: 75,       // ms
  p95Latency: 150,          // ms
  p99Latency: 300,          // ms
  originLatency: 200,       // ms (requests hitting origin)
  edgeLatency: 40            // ms (cached requests)
};

// Alert if cache hit ratio drops below 70%
if (metrics.cacheHitRatio < 70) {
  sendAlert('Low cache hit ratio - investigate TTL strategy');
}

Usage Metrics:

// Track CDN usage for cost optimization
usage = {
  bandwidthOut: '50 GB',
  requestsPerDay: 10000000,
  uniqueIPs: 500000,
  topPaths: [
    { path: '/images/logo.png', requests: 2000000, bytes: 10 },
    { path: '/api/products', requests: 1000000, bytes: 500 },
    { path: '/css/main.css', requests: 1500000, bytes: 50 }
  ]
};

Custom Analytics:

// Log cache behavior from edge functions
export default {
  async fetch(request, env) {
    const startTime = Date.now();
    const url = new URL(request.url);
    
    // Check cache
    const cached = await caches.default.match(request);
    const cacheHit = !!cached;
    
    const response = cached || (await fetch(request));
    const responseTime = Date.now() - startTime;
    
    // Log analytics
    await env.ANALYTICS.write({
      timestamp: new Date().toISOString(),
      path: url.pathname,
      cacheHit: cacheHit,
      responseTime: responseTime,
      status: response.status,
      country: request.headers.get('cf-ipcountry'),
      userAgent: request.headers.get('user-agent')
    });
    
    return response;
  }
};

// Analyze patterns
SELECT 
  path,
  COUNT(*) as requests,
  SUM(CASE WHEN cacheHit THEN 1 ELSE 0 END) / COUNT(*) as hitRatio,
  AVG(responseTime) as avgTime,
  PERCENTILE(responseTime, 0.95) as p95Time
FROM analytics
WHERE timestamp > NOW() - INTERVAL '24 hours'
GROUP BY path
ORDER BY requests DESC

Cost Optimization

Reducing CDN Costs

1. Cache Aggressively:

// More cache hits = lower costs
// Increase TTL where possible
GET /api/static-config
Cache-Control: public, max-age=86400  // 1 day

// Instead of
GET /api/static-config
Cache-Control: public, max-age=300    // 5 minutes
// Results in 288x more origin requests (288x higher cost)

2. Compress Content:

// Compression reduces bandwidth costs significantly

// In Node.js
const compression = require('compression');
app.use(compression());

// Typical reduction:
// - HTML: 70% reduction
// - JSON API: 60% reduction
// - JavaScript: 65% reduction
// Result: 3x less bandwidth transferred

// Cloudflare automatically enables brotli compression

3. Image Optimization:

// Images are 50%+ of bandwidth costs

// Bad: Serve full-size images
<img src="/images/product-full.jpg" />
// 5MB per image ร— 100K requests = 500 GB

// Good: Serve optimized images
<img 
  src="/images/product-large.webp"   // WebP format
  srcset="
    /images/product-small.webp 800w,
    /images/product-medium.webp 1200w,
    /images/product-large.webp 1600w
  "
/>
// 200KB + 400KB + 800KB = 1.4MB vs 5MB (72% savings)

// Or use responsive image service (Imgix, Cloudinary)
<img src="https://cdn.imgix.net/path/image.jpg?w=800&q=70&auto=format" />
// Auto-formats and compresses

4. Object Size Optimization:

// Smaller objects = lower costs

// API response bloat
{
  "products": [
    {
      "id": 1,
      "name": "Product",
      "description": "Very long description...", // Not needed
      "htmlDescription": "<html>...</html>",    // Not needed
      "internalNotes": "...",                   // Not needed
      "price": 99.99
    }
  ]
}

// Optimized response
{
  "products": [
    {
      "id": 1,
      "name": "Product",
      "price": 99.99
    }
  ]
}

// Result: 70% smaller response, proportional cost savings

5. Selective Caching:

// Don't cache everything - focus on high-volume paths

// Cache these (high volume, low change):
GET /api/products             // 100K req/day
GET /api/categories           // 50K req/day
GET /images/*                 // 500K req/day

// Don't cache these (low volume, user-specific):
GET /api/user/cart           // 10K req/day
GET /api/user/profile        // 15K req/day

// Result: Cache handles 85% of traffic with 15% of infrastructure cost

CDN Provider Comparison

Provider Cost Model Strengths Best For
Cloudflare $20-200/mo + usage Best value, easy setup, Workers Small-medium sites, edge functions
AWS CloudFront $0.085/GB AWS integration, pay-as-you-go AWS-heavy deployments
Fastly $0.12/GB + minimum Performance, real-time logs High-performance needs
Akamai Custom pricing Enterprise, best coverage Large enterprises
Bunny CDN $0.01-0.03/GB Cheapest bandwidth Cost-sensitive, high volume

Migration and Implementation Guide

Step 1: Audit Current Infrastructure

// Analyze current traffic
const analysis = {
  totalBandwidth: '500 GB/month',
  averageLatency: 350,      // ms
  topContent: [
    { type: 'Images', percent: 45 },
    { type: 'API', percent: 25 },
    { type: 'JavaScript', percent: 15 },
    { type: 'CSS', percent: 10 },
    { type: 'HTML', percent: 5 }
  ],
  userGeography: {
    'US': 40,
    'EU': 30,
    'APAC': 20,
    'Other': 10
  }
};

Step 2: Choose Provider and Plan

Decision matrix:

Small site (0-100K requests/day):
โ†’ Use Cloudflare (best value, easy setup)

Growing platform (100K-1M requests/day):
โ†’ Use Fastly or AWS CloudFront

High-performance needs:
โ†’ Use Fastly + optional edge functions

Enterprise (1M+ requests/day):
โ†’ Multi-CDN strategy with Akamai + others

Step 3: Configuration

// Update DNS to point to CDN
// Old: example.com A 203.0.113.1 (your server)
// New: example.com CNAME cdn.provider.com

// Set cache headers
app.use((req, res) => {
  if (req.path.match(/\.(js|css|jpg|png|gif|svg)$/)) {
    res.set('Cache-Control', 'public, max-age=31536000');
  } else if (req.path.startsWith('/api')) {
    res.set('Cache-Control', 'public, max-age=300');
  } else {
    res.set('Cache-Control', 'public, max-age=3600');
  }
});

Step 4: Testing and Monitoring

# Test from different locations
curl -I https://example.com
# Check for X-Cache headers

# Monitor latency improvements
# Before CDN: 350ms average
# After CDN: 80ms average (77% improvement)

# Monitor cache hit ratio
# Target: 85%+ cache hit ratio
# Below 70%: Review TTL strategy

Conclusion: Building for Global Scale

Key Takeaways

  1. CDNs reduce latency by serving content from geographic locations near users (80-90% faster than single origin)

  2. Edge functions extend CDN capabilities by allowing computation at the edge, enabling authentication, personalization, and smart routing (95%+ faster than origin)

  3. Cache effectively with appropriate TTLs based on content type:

    • Static assets: Long TTL with versioning
    • Dynamic content: Short TTL or no-cache
    • API responses: Medium TTL with selective purge
  4. Choose the right tool:

    • Static content only โ†’ Traditional CDN
    • Personalization needed โ†’ Edge functions
    • Complex computation โ†’ Origin server
  5. Monitor and optimize cache hit ratio (target 80%+) and edge performance

  6. Implement security with edge functions before requests reach origin

  7. Optimize costs through strategic caching, compression, and selective purging

Implementation Checklist

  • Configure Cache-Control headers for all content types
  • Implement asset versioning for static files
  • Choose appropriate TTLs for each content type
  • Deploy edge functions for authentication/validation
  • Set up cache monitoring and alerting
  • Create documentation for cache purging procedures
  • Test cache behavior in staging environment
  • Monitor origin server load after CDN deployment
  • Implement security headers at edge
  • Set up rate limiting and DDoS protection
  • Configure multi-CDN strategy if needed
  • Establish cost monitoring and optimization process

Next Steps

  1. Measure baseline: Check current latency and cache hit ratio
  2. Choose CDN provider: Cloudflare (best value), Fastly (performance), AWS CloudFront (AWS integration)
  3. Implement basic caching: Set Cache-Control headers
  4. Add edge functions: Start with authentication or validation
  5. Monitor and iterate: Use analytics to improve cache strategy
  6. Optimize costs: Review bandwidth usage and cache strategy

By combining CDN and edge computing effectively, you can build globally performant applications that serve users at lightning-fast speeds regardless of their location. The investment in proper caching and edge infrastructure pays dividends through improved user experience, reduced operational costs, and increased reliability.

Comments