Back to Blog
HTTPCachingPerformanceWeb Development

HTTP Caching Headers: Browser and CDN Caching

Master HTTP caching for better performance. Learn Cache-Control directives, ETags, and caching strategies.

B
Bootspring Team
Engineering
February 26, 2026
2 min read

Proper caching headers dramatically improve performance. This guide covers HTTP caching mechanisms.

Cache-Control Directives#

Common Directives#

Cache-Control: public # Cacheable by anyone Cache-Control: private # Only browser can cache Cache-Control: no-cache # Must revalidate Cache-Control: no-store # Never cache Cache-Control: max-age=3600 # Cache for 1 hour Cache-Control: s-maxage=86400 # CDN cache for 1 day Cache-Control: stale-while-revalidate=60 # Serve stale while refreshing Cache-Control: immutable # Never changes

Combined Directives#

1// Static assets with hash in filename 2res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); 3 4// API responses 5res.setHeader('Cache-Control', 'private, max-age=60, stale-while-revalidate=300'); 6 7// User-specific data 8res.setHeader('Cache-Control', 'private, no-cache'); 9 10// Sensitive data 11res.setHeader('Cache-Control', 'no-store');

ETags for Validation#

1import crypto from 'crypto'; 2 3function generateETag(content: string): string { 4 return `"${crypto.createHash('md5').update(content).digest('hex')}"`; 5} 6 7app.get('/api/data', async (req, res) => { 8 const data = await fetchData(); 9 const content = JSON.stringify(data); 10 const etag = generateETag(content); 11 12 // Check If-None-Match header 13 if (req.headers['if-none-match'] === etag) { 14 return res.status(304).end(); 15 } 16 17 res.setHeader('ETag', etag); 18 res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate'); 19 res.json(data); 20});

Last-Modified#

1app.get('/api/posts/:id', async (req, res) => { 2 const post = await getPost(req.params.id); 3 const lastModified = post.updatedAt.toUTCString(); 4 5 // Check If-Modified-Since 6 const ifModifiedSince = req.headers['if-modified-since']; 7 if (ifModifiedSince && new Date(ifModifiedSince) >= post.updatedAt) { 8 return res.status(304).end(); 9 } 10 11 res.setHeader('Last-Modified', lastModified); 12 res.setHeader('Cache-Control', 'private, max-age=60'); 13 res.json(post); 14});

Vary Header#

// Cache varies by these headers res.setHeader('Vary', 'Accept-Encoding, Accept-Language'); // For API versioning res.setHeader('Vary', 'Accept, Authorization');

Next.js Caching#

1// App Router 2export const revalidate = 3600; // Revalidate every hour 3 4// Route handlers 5export async function GET() { 6 return Response.json(data, { 7 headers: { 8 'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400', 9 }, 10 }); 11}

Common Patterns#

Static Assets#

location ~* \.(js|css|png|jpg|webp|woff2)$ { add_header Cache-Control "public, max-age=31536000, immutable"; }

HTML Pages#

location ~* \.html$ { add_header Cache-Control "no-cache"; }

API Responses#

// Public data res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=600'); // User-specific data res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');

Debugging#

# Check cache headers curl -I https://example.com/api/data # Force fresh request curl -H "Cache-Control: no-cache" https://example.com/api/data

Set appropriate TTLs, use ETags for validation, and leverage stale-while-revalidate for better UX.

Share this article

Help spread the word about Bootspring