How CDNs Improve Performance
A Content Delivery Network (CDN) is a globally distributed network of servers that cache and serve content from locations closest to users. CDNs reduce latency by eliminating long-distance data transfers, improve availability through redundancy, and reduce origin server load by absorbing traffic at the edge.
CDN Performance Benefits
- Reduced latency: Content served from edge nodes 10-50ms from users instead of 100-300ms
- Higher throughput: Distributed network absorbs traffic spikes without overloading origin
- Connection reuse: CDN maintains persistent connections to origin, reducing handshake overhead
- HTTP/3 and QUIC: Modern CDNs support the latest protocols for faster connections
- Compression: CDNs automatically apply Brotli/gzip compression at the edge
Cache-Control Headers Strategy
// next.config.js — Configure caching headers for different asset types
const nextConfig = {
async headers() {
return [
{
// Hashed static assets — cache forever (immutable)
source: '/_next/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
{
// Images with content hash — long cache
source: '/images/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=86400, stale-while-revalidate=604800',
},
],
},
{
// HTML pages — short cache with revalidation
source: '/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=0, s-maxage=60, stale-while-revalidate=300',
},
],
},
{
// API responses — no CDN cache by default
source: '/api/:path*',
headers: [
{
key: 'Cache-Control',
value: 'private, no-cache, no-store, must-revalidate',
},
],
},
];
},
};
module.exports = nextConfig;CDN Cache Keys and Variants
// Understanding CDN cache key composition
// Default cache key: URL (scheme + host + path + query string)
// Add Vary headers to create separate cache entries
// API route with varied caching
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const acceptLanguage = request.headers.get('accept-language') || 'en';
const language = acceptLanguage.split(',')[0].split('-')[0];
const data = await getLocalizedContent(language);
return NextResponse.json(data, {
headers: {
// CDN caches separate versions per language
'Vary': 'Accept-Language',
// s-maxage for CDN, max-age for browser
'Cache-Control': 'public, max-age=60, s-maxage=3600',
// Surrogate key for targeted cache purging
'Surrogate-Key': `content language-${language}`,
},
});
}
// Programmatic cache purging
async function purgeContentCache(contentId: string): Promise<void> {
// Purge by surrogate key (Fastly, Cloudflare)
await fetch('https://api.fastly.com/service/SERVICE_ID/purge/content', {
method: 'POST',
headers: {
'Fastly-Key': process.env.FASTLY_API_KEY!,
'Surrogate-Key': `content-${contentId}`,
},
});
}
// Cache warming — pre-populate CDN cache after deployment
async function warmCache(urls: string[]): Promise<void> {
const concurrency = 10;
for (let i = 0; i < urls.length; i += concurrency) {
const batch = urls.slice(i, i + concurrency);
await Promise.all(
batch.map(url =>
fetch(url, { headers: { 'X-Cache-Warm': 'true' } })
.then(res => console.log(`Warmed: ${url} (${res.status})`))
.catch(err => console.error(`Failed: ${url}`, err))
)
);
}
}Measuring CDN Performance
// Monitor CDN cache hit rates and latency
function analyzeCDNPerformance(): void {
const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[];
const cdnMetrics = resources.map(r => ({
url: new URL(r.name).pathname,
// Check Server-Timing for cache status
cacheStatus: r.serverTiming?.find(t => t.name === 'cache')?.description || 'unknown',
ttfb: r.responseStart - r.requestStart,
downloadTime: r.responseEnd - r.responseStart,
totalTime: r.responseEnd - r.startTime,
transferSize: r.transferSize,
protocol: r.nextHopProtocol,
}));
// Calculate cache hit rate
const hits = cdnMetrics.filter(m => m.cacheStatus === 'HIT').length;
const total = cdnMetrics.length;
console.log(`CDN Cache Hit Rate: ${((hits / total) * 100).toFixed(1)}%`);
// Average latency comparison
const hitLatency = cdnMetrics
.filter(m => m.cacheStatus === 'HIT')
.reduce((sum, m) => sum + m.ttfb, 0) / hits || 0;
const missLatency = cdnMetrics
.filter(m => m.cacheStatus === 'MISS')
.reduce((sum, m) => sum + m.ttfb, 0) / (total - hits) || 0;
console.log(`Avg TTFB — Cache HIT: ${hitLatency.toFixed(0)}ms, MISS: ${missLatency.toFixed(0)}ms`);
}CDN Optimization Checklist
- Use immutable caching: Hash-based filenames enable max-age=31536000, immutable
- Separate cache strategies: Different TTLs for static assets, HTML, and API responses
- Use stale-while-revalidate: Serve stale content while fetching fresh content in the background
- Minimize Vary headers: Each Vary value multiplies cache entries, reducing hit rates
- Warm cache after deploys: Pre-populate critical pages to avoid cold starts
- Monitor hit rates: Target 90%+ cache hit rates for static assets