Caching Strategies for Web Applications: Browser Cache, CDN, and Application Cache | SoniNow Blog

Limited TimeLearn More

cachingcdnbrowser cacheperformanceweb optimization

Caching Strategies for Web Applications: Browser Cache, CDN, and Application Cache

Published

2026-06-23

Read Time

4 mins

Caching Strategies for Web Applications: Browser Cache, CDN, and Application Cache

Caching is the single most impactful performance optimization available to web developers. A well-executed caching strategy reduces server load, eliminates redundant network requests, and delivers near-instant page loads for returning visitors. But caching is also notoriously easy to get wrong—stale data, cache poisoning, and invalidation headaches plague teams that treat caching as an afterthought. A layered approach spanning browser cache, CDN, and application-level cache builds resilience without sacrificing freshness.

Browser Cache with Cache-Control Headers

The browser cache is the first line of defense. Setting appropriate Cache-Control headers on your responses tells the browser what to cache and for how long:

# Immutable static assets (fingerprinted filenames)
Cache-Control: public, max-age=31536000, immutable

# HTML pages (short TTL with revalidation)
Cache-Control: public, max-age=0, must-revalidate

# API responses (no browser caching)
Cache-Control: no-store

Fingerprinted assets (bundles with content hashes in filenames) are safe to cache indefinitely because a new deployment changes the filename. For HTML pages, use must-revalidate combined with ETag headers so the browser can make conditional requests:

// Next.js route handler with ETag
export async function GET(req: NextRequest) {
  const data = await fetchData()
  const etag = generateHash(JSON.stringify(data))

  if (req.headers.get('if-none-match') === etag) {
    return new Response(null, { status: 304 })
  }

  return Response.json(data, {
    headers: { 'ETag': etag, 'Cache-Control': 'public, max-age=0, must-revalidate' },
  })
}

Conditional requests return a 304 Not Modified response with an empty body, saving bandwidth while keeping the cached version fresh.

CDN Caching for Global Distribution

Content Delivery Networks cache responses at edge locations close to users. Configure CDN caching through the s-maxage directive in Cache-Control or through the CDN provider's settings:

# CDN caches for 1 hour, browser for 1 minute
Cache-Control: public, max-age=60, s-maxage=3600, stale-while-revalidate=86400

The stale-while-revalidate directive is particularly powerful. It allows the CDN to serve stale content immediately while fetching a fresh version in the background. This eliminates cache misses entirely for cacheable content:

// API route with stale-while-revalidate pattern
const data = await fetchFromDatabase()

return Response.json(data, {
  headers: {
    'Cache-Control': 'public, max-age=300, stale-while-revalidate=3600',
  },
})

With this configuration, users never wait for the API—the CDN either serves fresh content or stale content that's being refreshed asynchronously.

Service Worker Caching with the Cache API

Service workers act as a programmable proxy between the browser and the network. They enable offline support and advanced caching strategies beyond what HTTP headers provide:

// Service worker with cache-first strategy for assets
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/static/')) {
    event.respondWith(
      caches.match(event.request).then((cached) => {
        return cached || fetchAndCache(event.request)
      })
    )
  }

  // Network-first for API calls
  if (event.request.url.includes('/api/')) {
    event.respondWith(
      fetch(event.request)
        .then((response) => {
          cacheResponse(event.request, response.clone())
          return response
        })
        .catch(() => caches.match(event.request))
    )
  }
})

Cache-first serves assets instantly from the service worker without touching the network. Network-first tries the live API and falls back to cached data when offline. Use a versioned cache name (e.g., static-v2) and delete old caches during the activate event to prevent stale storage buildup.

Application-Level Caching with Redis

For server-side caching that persists across instances and survives restarts, Redis is the industry standard. Use it to cache expensive computations, database query results, and rendered fragments:

import { Redis } from '@upstash/redis'

const redis = new Redis({ url: process.env.REDIS_URL!, token: process.env.REDIS_TOKEN! })

export async function getCachedProducts(categoryId: string) {
  const cacheKey = `products:category:${categoryId}`

  // Try cache first
  const cached = await redis.get(cacheKey)
  if (cached) return JSON.parse(cached as string)

  // Compute and cache
  const products = await db.product.findMany({ where: { categoryId } })
  await redis.setex(cacheKey, 300, JSON.stringify(products))
  return products
}

Cache Invalidation Strategies

Invalidation is the hardest part of caching. The most reliable pattern is cache tagging—assign tags to cached entries and purge by tag when data changes:

// Tag-based invalidation
await redis.setex(cacheKey, 300, JSON.stringify(products))
await redis.sadd(`cache:tags:product:${product.id}`, cacheKey)

// When a product updates
async function invalidateProduct(productId: string) {
  const keys = await redis.smembers(`cache:tags:product:${productId}`)
  if (keys.length > 0) await redis.del(...keys)
  await redis.del(`cache:tags:product:${productId}`)
}

For CDN-level invalidation, most providers support purge-by-tag or purge-by-pattern. Combined with short TTLs and stale-while-revalidate, you minimize the window where stale data is served while maintaining cache hit rates above 90%.

A layered caching strategy—browser, CDN, service worker, and application-level—creates a resilient system where each layer compensates for the others' weaknesses. Start with aggressive browser caching for assets, add CDN caching with stale-while-revalidate for dynamic content, and use Redis for server-side optimization.

<a href="/services/web-development">Our team builds high-performance web applications</a> with caching strategies that scale. Contact us to audit your current performance and identify caching wins.