CDN Optimization Strategies: Cache Headers, Edge Computing, Cloudflare Workers
CDN optimization strategies: Cache-Control headers, CDN cache rules, Cloudflare Workers edge computing, asset optimization, cache invalidation, and multi-CDN failover.
A well-configured CDN can serve the majority of your traffic without a single request reaching your origin server. Poor cache configuration, on the other hand, turns your CDN into an expensive proxy that adds latency instead of removing it. This guide covers every CDN optimization lever: cache headers, edge logic with Cloudflare Workers, asset optimization, and invalidation strategies.
Cache-Control Directives: The Complete Reference
The Cache-Control response header is the primary way you communicate caching intent to both browsers and CDNs.
Key Directives
Cache-Control: public, max-age=31536000, immutable
| Directive | Meaning |
|---|---|
public | Can be cached by CDN (and browsers) |
private | Only browser cache, not CDN |
no-cache | Must revalidate with origin before serving (not βno cachingβ) |
no-store | Never cache β for sensitive data |
max-age=N | Browser caches for N seconds |
s-maxage=N | CDN caches for N seconds (overrides max-age for CDNs) |
stale-while-revalidate=N | Serve stale while fetching fresh in background |
stale-if-error=N | Serve stale if origin returns error |
immutable | Resource will never change (skip revalidation) |
Caching Strategy by Asset Type
// Express middleware β set headers based on path
app.use((req, res, next) => {
const path = req.path;
if (path.match(/\.(js|css|woff2?|ttf|eot)$/) && path.includes('.[hash].')) {
// Hashed static assets β cache forever
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
} else if (path.match(/\.(png|jpg|jpeg|webp|avif|svg|gif|ico)$/)) {
// Images β long cache with stale-while-revalidate
res.setHeader('Cache-Control', 'public, max-age=86400, stale-while-revalidate=604800');
} else if (path.startsWith('/api/')) {
// API responses β short cache, revalidate in background
res.setHeader('Cache-Control', 'public, s-maxage=60, stale-while-revalidate=300');
} else if (path === '/') {
// HTML index β short CDN cache, always revalidate
res.setHeader('Cache-Control', 'public, max-age=0, s-maxage=300, must-revalidate');
} else {
// Default HTML pages
res.setHeader('Cache-Control', 'public, max-age=0, s-maxage=60, stale-while-revalidate=300');
}
next();
});
Vary Header for Content Negotiation
Tell the CDN to maintain separate cache entries for different request variants:
// Separate cache for different encodings
res.setHeader('Vary', 'Accept-Encoding');
// Separate cache for mobile vs desktop (use sparingly β multiplies cache size)
res.setHeader('Vary', 'Accept-Encoding, User-Agent');
// For internationalized sites
res.setHeader('Vary', 'Accept-Language, Accept-Encoding');
Cloudflare Cache Rules
In the Cloudflare dashboard (or via API/Terraform), set Cache Rules to override origin headers or cache paths the origin marks as uncacheable:
# Terraform β Cloudflare Cache Rule
resource "cloudflare_ruleset" "cache_rules" {
zone_id = var.zone_id
name = "Cache optimization rules"
kind = "zone"
phase = "http_response_headers_transform"
rules {
action = "set_cache_settings"
action_parameters {
edge_ttl {
mode = "override_origin"
default = 3600 # 1 hour default
}
browser_ttl {
mode = "override_origin"
default = 3600
}
}
expression = "(http.request.uri.path matches \"^/static/\")"
description = "Cache static assets for 1 hour"
enabled = true
}
}
Cloudflare Workers for Edge Logic
Cloudflare Workers run JavaScript at the edge β in 275+ data centers worldwide, milliseconds from your users. Use them to run logic without hitting your origin server.
Edge A/B Testing
// workers/ab-test.js
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Only run A/B test on homepage
if (url.pathname !== '/') {
return fetch(request);
}
// Assign variant based on cookie or random
const cookie = request.headers.get('Cookie') || '';
const existingVariant = cookie.match(/ab_variant=([ab])/)?.[1];
const variant = existingVariant || (Math.random() < 0.5 ? 'a' : 'b');
// Fetch appropriate version
const targetUrl = variant === 'b'
? new URL('/experiments/homepage-v2', url)
: request.url;
const response = await fetch(targetUrl, request);
const newResponse = new Response(response.body, response);
// Set variant cookie if not present
if (!existingVariant) {
newResponse.headers.append('Set-Cookie',
`ab_variant=${variant}; Path=/; Max-Age=604800; SameSite=Lax`
);
}
return newResponse;
},
};
Edge Authentication Check
Validate JWTs at the edge to reject unauthenticated requests before they reach your origin:
// workers/auth-guard.js
import { jwtVerify, importSPKI } from 'jose';
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Only protect /api/ routes
if (!url.pathname.startsWith('/api/')) {
return fetch(request);
}
const authHeader = request.headers.get('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
try {
const token = authHeader.slice(7);
const publicKey = await importSPKI(env.JWT_PUBLIC_KEY, 'RS256');
const { payload } = await jwtVerify(token, publicKey);
// Forward user info to origin via header
const modifiedRequest = new Request(request, {
headers: {
...Object.fromEntries(request.headers),
'X-User-Id': payload.sub,
'X-User-Role': payload.role,
},
});
return fetch(modifiedRequest);
} catch {
return new Response(JSON.stringify({ error: 'Invalid token' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
},
};
Image Optimization at Edge
// workers/image-resize.js
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Cloudflare Image Resizing (requires Pro plan)
if (url.pathname.startsWith('/images/')) {
const accept = request.headers.get('Accept') || '';
const format = accept.includes('avif') ? 'avif'
: accept.includes('webp') ? 'webp' : 'jpeg';
const width = parseInt(url.searchParams.get('w') || '800', 10);
return fetch(request, {
cf: {
image: {
width: Math.min(width, 2000), // cap at 2000px
format,
quality: 85,
},
},
});
}
return fetch(request);
},
};
Cache Invalidation Strategies
Cache invalidation is famously hard. Here are the main strategies:
1. Hash-Based Cache Busting (Best)
Include a content hash in the filename β zero invalidation needed, content changes trigger a new URL automatically:
/static/app.a3f9c12.js β app changes β /static/app.7d2a841.js
Configure your bundler (Vite, Webpack) to output hashed filenames:
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
entryFileNames: '[name].[hash].js',
chunkFileNames: '[name].[hash].js',
assetFileNames: '[name].[hash].[ext]',
},
},
},
};
2. Cloudflare Cache Purge API
For content that changes without URL changes (HTML pages, API responses):
async function purgeCloudflareCache(urls) {
const response = await fetch(
`https://api.cloudflare.com/client/v4/zones/${process.env.CF_ZONE_ID}/purge_cache`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CF_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ files: urls }),
}
);
const data = await response.json();
if (!data.success) {
throw new Error(`Cache purge failed: ${JSON.stringify(data.errors)}`);
}
return data;
}
// Purge on CMS publish
app.post('/webhooks/cms-publish', async (req, res) => {
const { slug } = req.body;
await purgeCloudflareCache([
`https://yourdomain.com/blog/${slug}`,
`https://yourdomain.com/blog`, // also purge listing page
]);
res.json({ purged: true });
});
3. Surrogate Keys (Cache Tags)
Tag cache entries and purge all entries with a given tag at once:
// Set Surrogate-Key header on response
app.get('/blog/:slug', async (req, res) => {
const post = await getPost(req.params.slug);
// Tag the cached response with content identifiers
res.setHeader('Surrogate-Key', `post-${post.id} author-${post.authorId} blog`);
res.setHeader('Cache-Control', 'public, s-maxage=3600');
res.json(post);
});
// Purge all posts by a specific author with one API call
async function purgeAuthorContent(authorId) {
await purgeByTag(`author-${authorId}`);
}
Multi-CDN Failover
For maximum availability, route traffic through multiple CDNs using DNS failover or a traffic manager:
// Simple multi-CDN with fetch failover
const CDN_ENDPOINTS = [
'https://cdn1.yourdomain.com',
'https://cdn2.yourdomain.com',
];
async function fetchWithCDNFailover(path, options = {}) {
for (const endpoint of CDN_ENDPOINTS) {
try {
const response = await fetch(`${endpoint}${path}`, {
...options,
signal: AbortSignal.timeout(3000), // 3s timeout per CDN
});
if (response.ok) return response;
} catch (err) {
console.warn(`CDN ${endpoint} failed:`, err.message);
}
}
throw new Error('All CDN endpoints failed');
}
CDN Optimization Checklist
- Hashed filenames for all JS/CSS/font assets (immutable caching)
-
Cache-Control: public, max-age=31536000, immutablefor hashed assets -
stale-while-revalidateon HTML pages for zero-latency updates -
Vary: Accept-Encodingset on all compressible responses - Brotli compression enabled on CDN (better ratio than gzip)
- Cloudflare Cache Rules override uncacheable origin headers where appropriate
- Cache purge triggered on every deploy for HTML/API responses
- Cloudflare Workers handling auth/redirects/A-B tests at edge
- Image optimization (WebP/AVIF conversion, resizing) done at edge
- Cache hit ratio monitored in CDN analytics (target >95% for static assets)
A properly configured CDN should handle 90-95% of your traffic without hitting your origin. If your CDN analytics show a cache hit ratio below 80%, start by reviewing your Cache-Control headers β most configurations default to non-cacheable when in doubt.
50 CLI Commands That Save Hours Every Week
The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.
- Git mastery (10 cmds)
- File & search wizardry
- Docker & DevOps tricks
- Process & network tools
- Productivity boosters
- macOS & Linux install guide
No spam. 1 email/week max. Unsubscribe anytime.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links β we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.