CDNs serve content from locations close to users. This guide covers configuring CDNs for optimal performance.
How CDNs Work#
User (Tokyo) ─────┐
│
User (NYC) ───────┼──> Edge Server ──> Origin Server
│ (Cached)
User (London) ────┘
Cloudflare Configuration#
Cache Rules#
1// cloudflare worker for custom caching
2export default {
3 async fetch(request) {
4 const url = new URL(request.url);
5
6 // Static assets - long cache
7 if (url.pathname.match(/\.(js|css|png|jpg|webp|woff2)$/)) {
8 const response = await fetch(request);
9 const newResponse = new Response(response.body, response);
10 newResponse.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
11 return newResponse;
12 }
13
14 // API responses - short cache
15 if (url.pathname.startsWith('/api/')) {
16 const response = await fetch(request);
17 const newResponse = new Response(response.body, response);
18 newResponse.headers.set('Cache-Control', 's-maxage=60, stale-while-revalidate=300');
19 return newResponse;
20 }
21
22 return fetch(request);
23 },
24};Page Rules#
1# Static assets
2Pattern: *.example.com/*.(css|js|png|jpg|webp|woff2)
3Cache Level: Cache Everything
4Edge Cache TTL: 1 month
5Browser Cache TTL: 1 year
6
7# HTML pages
8Pattern: *.example.com/*.html
9Cache Level: Cache Everything
10Edge Cache TTL: 1 hour
11
12# API endpoints
13Pattern: api.example.com/*
14Cache Level: BypassCache Headers#
1// Next.js API route caching
2export async function GET() {
3 const data = await fetchData();
4
5 return Response.json(data, {
6 headers: {
7 // Cache at CDN for 1 hour
8 'CDN-Cache-Control': 'max-age=3600',
9 // Cache in browser for 1 minute
10 'Cache-Control': 'public, max-age=60, stale-while-revalidate=300',
11 // Vary by accept-encoding
12 'Vary': 'Accept-Encoding',
13 },
14 });
15}Cache Invalidation#
1// Cloudflare purge API
2async function purgeCache(urls: string[]) {
3 await fetch(`https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache`, {
4 method: 'POST',
5 headers: {
6 'Authorization': `Bearer ${CF_TOKEN}`,
7 'Content-Type': 'application/json',
8 },
9 body: JSON.stringify({ files: urls }),
10 });
11}
12
13// Purge by tag
14async function purgeByTag(tags: string[]) {
15 await fetch(`https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache`, {
16 method: 'POST',
17 headers: {
18 'Authorization': `Bearer ${CF_TOKEN}`,
19 'Content-Type': 'application/json',
20 },
21 body: JSON.stringify({ tags }),
22 });
23}Cache Tags#
1// Add cache tags for granular purging
2export async function GET() {
3 const product = await getProduct(id);
4
5 return Response.json(product, {
6 headers: {
7 'Cache-Tag': `product-${id}, category-${product.categoryId}`,
8 'Cache-Control': 's-maxage=3600',
9 },
10 });
11}Performance Tips#
- Use immutable for versioned assets:
max-age=31536000, immutable - Enable Brotli compression: Smaller file sizes
- Use stale-while-revalidate: Better perceived performance
- Cache API responses: Even short TTLs help
- Monitor cache hit ratio: Target >90% for static content
Configure appropriate TTLs, implement cache invalidation, and monitor cache performance.