Skip to main content
Web DevelopmentSoftware Engineering

The Developer's Guide to HTTP Caching

Master HTTP caching headers, CDN strategies, and cache invalidation to deliver blazing-fast experiences while keeping content fresh.

Prince Mbeah Essilfie

Prince Mbeah Essilfie

April 24, 2026 at 06:04 AM

9 min read 1945 views
The Developer's Guide to HTTP Caching

Caching Is the Easiest Performance Win

A cached response takes 0ms server time. No optimization beats not doing the work at all.

Cache-Control Header

The single most important caching mechanism:

Cache-Control: public, max-age=3600, s-maxage=86400, stale-while-revalidate=60

Breaking it down:

  • public — CDNs and browsers can cache this
  • max-age=3600 — Browser cache: 1 hour
  • s-maxage=86400 — CDN cache: 24 hours
  • stale-while-revalidate=60 — Serve stale content while revalidating in background

Caching Strategies by Content Type

// server/middleware/cache-headers.ts
export default defineEventHandler((event) => {
  const path = event.path

  // Static assets: cache forever (use hashed filenames)
  if (path.match(/\.(js|css|woff2|png|jpg|svg)$/)) {
    setHeader(event, 'Cache-Control', 'public, max-age=31536000, immutable')
    return
  }

  // API data: short cache with revalidation
  if (path.startsWith('/api/posts') && event.method === 'GET') {
    setHeader(event, 'Cache-Control', 'public, s-maxage=60, stale-while-revalidate=300')
    return
  }

  // HTML pages: no browser cache, CDN caches briefly
  if (!path.startsWith('/api')) {
    setHeader(event, 'Cache-Control', 'public, s-maxage=300, stale-while-revalidate=60')
    return
  }
})

ETag for Conditional Requests

export default defineEventHandler(async (event) => {
  const post = await getPost(event)
  const etag = `"${hashContent(JSON.stringify(post))}"`

  setHeader(event, 'ETag', etag)

  // If client already has this version, return 304
  const ifNoneMatch = getHeader(event, 'if-none-match')
  if (ifNoneMatch === etag) {
    setResponseStatus(event, 304)
    return null
  }

  return post
})
ETags save bandwidth — a 304 response has no body. The browser reuses its cached version, and the server avoids serializing/transmitting the full response.

Cache Invalidation

The two hard problems in computer science: cache invalidation, naming things, and off-by-one errors.

Strategy 1: Time-based (TTL)

s-maxage=60  ← Content is at most 60 seconds stale

Strategy 2: Event-based purging

// After updating a post, purge CDN cache
async function updatePost(id: string, data: UpdateInput) {
  const post = await prisma.post.update({ where: { id }, data })
  await purgeCDNCache(`/api/posts/${post.slug}`)
  await purgeCDNCache('/api/posts') // List endpoint too
  return post
}

Strategy 3: Versioned URLs

<!-- Hash in filename = cache forever, new file = new URL -->
<script src="/app.a3f2b1c.js"></script>

Common Mistakes

  1. No-cache doesn't mean "don't cache" — It means "revalidate before using cache"
  2. Forgetting Vary header — Different content per Accept-Encoding? Add Vary: Accept-Encoding
  3. Caching authenticated responses — Use private or no-store for user-specific data
  4. No cache-busting for assets — Without hashed filenames, users get stale CSS/JS

Decision Quick-Reference

ContentStrategy
Hashed static assetsimmutable, max-age=1y
HTML pagess-maxage=5m, stale-while-revalidate=1m
Public API (lists)s-maxage=1m, stale-while-revalidate=5m
User-specific dataprivate, no-cache
Real-time datano-store

Caching done right is invisible to users and transformative for performance.

DevOpsPerformanceAPI Design
Prince Mbeah Essilfie

Written by Prince Mbeah Essilfie

Fullstack Software Developer

Read next

The Complete Guide to Vue 3 Composables
12 min read

The Complete Guide to Vue 3 Composables

Understand the power of Vue 3's Composition API through practical, real-world composable patterns that you can use today.

Prince Mbeah EssilfiePrince Mbeah Essilfie
1.1K