Caching
CDN Caching
CDN strategies for static and dynamic content.
CDN Caching
Content Delivery Networks (CDNs) are the first line of defense for performance. They cache content at the edge, closer to your users.
CDN Caching Headers
Cache-Control
The most important header for controlling CDN behavior.
# Public assets (JS, CSS, images)
Cache-Control: public, max-age=31536000, immutable
# HTML pages
Cache-Control: public, max-age=0, must-revalidate
# API responses
Cache-Control: public, max-age=60, s-maxage=300Breakdown:
public: Can be cached by CDNs and browsersprivate: Only cache by browser, not CDNmax-age: Browser cache durations-maxage: CDN cache duration (overrides max-age for CDNs)immutable: Content never changes (perfect for hashed assets)
ETag for Validation
// Generate ETag based on content
function generateETag(content) {
return `"${crypto.createHash('md5').update(content).digest('hex')}"`;
}
// Check for conditional requests
app.get('/api/data', (req, res) => {
const data = fetchData();
const etag = generateETag(JSON.stringify(data));
res.set('ETag', etag);
if (req.headers['if-none-match'] === etag) {
return res.status(304).end(); // Not Modified
}
res.json(data);
});Static Asset Caching
Versioned Assets
// Build step: Hash filenames for cache busting
const assets = {
'main.js': 'main.a1b2c3d4.js',
'styles.css': 'styles.e5f6g7h8.css'
};
// HTML references versioned assets
<html>
<link rel="stylesheet" href="/assets/styles.e5f6g7h8.css">
<script src="/assets/main.a1b2c3d4.js"></script>
</html>CDN Configuration
// Cloudflare Worker for edge caching
export default {
async fetch(request) {
const url = new URL(request.url);
// Cache API responses
if (url.pathname.startsWith('/api/')) {
const cacheKey = new Request(url, request);
const cache = caches.default;
let response = await cache.match(cacheKey);
if (!response) {
response = await fetch(request);
// Cache for 5 minutes
response = new Response(response.body, response);
response.headers.set('Cache-Control', 'public, max-age=300');
cache.put(cacheKey, response.clone());
}
return response;
}
return fetch(request);
}
};Dynamic Content Caching
Edge-Side Includes (ESI)
<!-- Cache the layout but not user-specific content -->
<esi:include src="/api/user-header" />
<div class="content">
<esi:include src="/api/content/123" />
</div>Stale-While-Revalidate
// Serve stale content while refreshing
app.get('/api/products', async (req, res) => {
const cacheKey = 'products:list';
const cached = await redis.get(cacheKey);
// Set cache control for CDN
res.set({
'Cache-Control': 'public, max-age=60, stale-while-revalidate=300',
'Vary': 'Accept-Encoding'
});
if (cached) {
// Return cached immediately, refresh async
refreshProductsCache(cacheKey);
return res.json(JSON.parse(cached));
}
const products = await fetchProducts();
await redis.setex(cacheKey, 300, JSON.stringify(products));
res.json(products);
});Geographic Personalization
// Cache different versions by location
app.get('/api/content', (req, res) => {
const country = req.headers['cf-ipcountry'] || 'US';
const cacheKey = `content:${country}`;
res.set({
'Cache-Control': 'public, max-age=3600',
'Vary': 'cf-ipcountry' // CDN creates separate cache per country
});
// Return country-specific content
res.json(getContentForCountry(country));
});Cache Invalidation Strategies
Time-Based Invalidation
// Assets expire naturally based on Cache-Control
// Best for static content with long TTLsPurge API
// Purge specific URLs from CDN
async function purgeCDN(urls) {
await fetch('https://api.cloudflare.com/client/v4/zones/zone_id/purge_cache', {
method: 'POST',
headers: {
'Authorization': 'Bearer token',
'Content-Type': 'application/json'
},
body: JSON.stringify({ files: urls })
});
}
// Purge when content changes
await purgeCDN([
'https://cdn.example.com/api/products',
'https://cdn.example.com/api/user/123'
]);Cache Tags
// Tag content for bulk invalidation
app.get('/api/products', (req, res) => {
res.set({
'Cache-Control': 'public, max-age=3600',
'Cache-Tag': 'products,category:electronics' // Custom header
});
});
// Purge by tag
await fetch('/cdn/purge/tag', {
method: 'POST',
body: JSON.stringify({ tags: ['products'] })
});Advanced CDN Patterns
API Response Caching
// Smart API caching middleware
function cdnCache(options = {}) {
const {
ttl = 300, // 5 minutes default
keyGenerator = req => req.url,
vary = ['Accept-Encoding']
} = options;
return async (req, res, next) => {
// Skip caching for non-GET requests
if (req.method !== 'GET') return next();
const cacheKey = keyGenerator(req);
const cached = await redis.get(cacheKey);
if (cached) {
const data = JSON.parse(cached);
res.set({
'Cache-Control': `public, max-age=${ttl}`,
'Vary': vary.join(', '),
'X-Cache': 'HIT'
});
return res.json(data);
}
// Capture response
const originalJson = res.json;
res.json = function(data) {
// Cache the response
redis.setex(cacheKey, ttl, JSON.stringify(data));
res.set({
'Cache-Control': `public, max-age=${ttl}`,
'Vary': vary.join(', '),
'X-Cache': 'MISS'
});
return originalJson.call(this, data);
};
next();
};
}
// Usage
app.get('/api/users', cdnCache({ ttl: 600 }), getUserList);Image Optimization at Edge
// Cloudinary-style image transformations
app.get('/images/:path(*)', async (req, res) => {
const { path } = req.params;
const { width, height, format = 'auto' } = req.query;
// Generate variation key
const key = `image:${path}:${width}:${height}:${format}`;
res.set({
'Cache-Control': 'public, max-age=31536000, immutable',
'Vary': 'Accept'
});
// Check edge cache
const cached = await redis.get(key);
if (cached) return res.send(cached);
// Transform and cache
const optimized = await transformImage(path, { width, height, format });
await redis.set(key, optimized);
res.send(optimized);
});CDN Cache Busting
Never use ?v=123 query parameters for cache busting. Some CDNs/proxies ignore query parameters for caching.
Use filename hashing instead:
styles.css→styles.a1b2c3d4.cssapp.js→app.e5f6g7h8.js
CDN Selection Guide
Cloudflare: Best for ease of use, security, and free tier AWS CloudFront: Best if you're already in AWS ecosystem Fastly: Best for real-time purging and edge computing Akamai: Best for enterprise needs and global scale