TechLead
Lesson 12 of 22
5 min read
Performance Engineering

CDN Optimization

Configure CDNs for maximum cache hit rates, optimal headers, and intelligent content delivery strategies

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

Continue Learning