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:
- Your origin server handles all dynamic requests
- CDN caches static content close to users
- For dynamic content, requests still go to origin
- 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)
Hybrid Approach (Recommended for Most Projects)
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
- 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);
});
- 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
- 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
- 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
}
};
- 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);
}
};
- 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
-
CDNs reduce latency by serving content from geographic locations near users (80-90% faster than single origin)
-
Edge functions extend CDN capabilities by allowing computation at the edge, enabling authentication, personalization, and smart routing (95%+ faster than origin)
-
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
-
Choose the right tool:
- Static content only โ Traditional CDN
- Personalization needed โ Edge functions
- Complex computation โ Origin server
-
Monitor and optimize cache hit ratio (target 80%+) and edge performance
-
Implement security with edge functions before requests reach origin
-
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
- Measure baseline: Check current latency and cache hit ratio
- Choose CDN provider: Cloudflare (best value), Fastly (performance), AWS CloudFront (AWS integration)
- Implement basic caching: Set Cache-Control headers
- Add edge functions: Start with authentication or validation
- Monitor and iterate: Use analytics to improve cache strategy
- 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