Claude
Skills
Sign in
Back

advanced-caching-strategies

Included with Lifetime
$97 forever

Multi-layer caching strategies across CDN, browser, server, and database. PROACTIVELY activate for: (1) HTTP caching headers, (2) CDN configuration, (3) Server-side caching with Redis, (4) Cache invalidation strategies, (5) ETag implementation. Triggers: "caching", "cache strategy", "CDN", "browser cache", "server cache", "redis", "cache invalidation", "ETag", "Cache-Control"

Backend & APIs

What this skill does


# Advanced Caching Strategies

This skill provides guidance on designing and implementing multi-layered caching strategies across the full application stack (CDN, browser, server, database) to minimize latency, reduce server load, and improve overall application resilience and user experience.

## The Cache Hierarchy

Caching can be implemented at multiple layers, from closest to the user to closest to the data source:

1. **Browser Cache**: Client-side caching (HTTP caching, LocalStorage, IndexedDB)
2. **CDN Edge Cache**: Geographically distributed caching (Vercel Edge, CloudFront, Cloudflare)
3. **Server-Side Cache**: Application-level caching (Redis, in-memory)
4. **Database Cache**: Query result caching, connection pooling

**Goal**: Serve content from the closest, fastest cache layer possible.

## Browser Caching

Browser caching reduces network requests by storing resources locally.

### HTTP Cache-Control Headers

The `Cache-Control` header controls how and for how long browsers cache responses.

#### Immutable Static Assets

```http
# For assets with content-addressed filenames (e.g., app.abc123.js)
Cache-Control: public, max-age=31536000, immutable
```

- **public**: Can be cached by browsers and CDNs
- **max-age=31536000**: Cache for 1 year (in seconds)
- **immutable**: Never revalidate (file name changes when content changes)

**Next.js**: Automatically sets this for `/_next/static/` files

#### Dynamic HTML Pages

```http
# For HTML pages that change periodically
Cache-Control: public, max-age=0, must-revalidate
```

- **max-age=0**: Revalidate on every request
- **must-revalidate**: Must check server before using stale cache

Or with stale-while-revalidate:
```http
Cache-Control: public, max-age=60, stale-while-revalidate=86400
```

- Serve from cache for 60s
- If 60s-24h old, serve stale content while revalidating in background

#### API Responses

```http
# User-specific data (don't cache)
Cache-Control: private, no-store

# Public data that changes occasionally
Cache-Control: public, max-age=300, stale-while-revalidate=600
```

- **private**: Only browser cache (not CDN)
- **no-store**: Don't cache at all
- **stale-while-revalidate**: Serve stale data while fetching fresh data

### ETag and Conditional Requests

ETags enable efficient revalidation without re-downloading unchanged content.

#### Server Implementation (Next.js API Route)

```ts
// app/api/data/route.ts
import { NextResponse } from 'next/server'
import crypto from 'crypto'

export async function GET(request: Request) {
  const data = await fetchData()
  const content = JSON.stringify(data)

  // Generate ETag from content hash
  const etag = crypto.createHash('md5').update(content).digest('hex')

  // Check If-None-Match header
  const clientEtag = request.headers.get('if-none-match')

  if (clientEtag === etag) {
    // Content hasn't changed
    return new NextResponse(null, { status: 304 })
  }

  // Content changed, send full response
  return NextResponse.json(data, {
    headers: {
      'ETag': etag,
      'Cache-Control': 'public, max-age=300',
    },
  })
}
```

#### Python FastAPI Implementation

```python
from fastapi import FastAPI, Request, Response
import hashlib
import json

app = FastAPI()

@app.get("/api/data")
async def get_data(request: Request):
    data = await fetch_data()
    content = json.dumps(data)

    # Generate ETag
    etag = hashlib.md5(content.encode()).hexdigest()

    # Check If-None-Match
    if request.headers.get("if-none-match") == etag:
        return Response(status_code=304)

    # Return with ETag
    return Response(
        content=content,
        headers={
            "ETag": etag,
            "Cache-Control": "public, max-age=300"
        }
    )
```

### LocalStorage and IndexedDB

For application state and data that doesn't need to be in HTTP cache.

```ts
// Simple cache wrapper for localStorage
class BrowserCache {
  static set(key: string, value: any, ttlMs: number) {
    const item = {
      value,
      expiry: Date.now() + ttlMs,
    }
    localStorage.setItem(key, JSON.stringify(item))
  }

  static get(key: string) {
    const itemStr = localStorage.getItem(key)
    if (!itemStr) return null

    const item = JSON.parse(itemStr)
    if (Date.now() > item.expiry) {
      localStorage.removeItem(key)
      return null
    }

    return item.value
  }
}

// Usage
BrowserCache.set('user-prefs', preferences, 7 * 24 * 60 * 60 * 1000) // 7 days
const prefs = BrowserCache.get('user-prefs')
```

## CDN Caching

CDNs cache content at edge locations close to users, reducing latency and server load.

### Vercel Edge Network (Next.js)

Vercel automatically caches static assets and certain dynamic routes.

```ts
// app/products/page.tsx
export const revalidate = 3600 // Cache for 1 hour

export default async function ProductsPage() {
  const products = await fetch('https://api.example.com/products', {
    next: { revalidate: 3600 }
  }).then(r => r.json())

  return <ProductGrid products={products} />
}
```

### Custom CDN Headers

```ts
// app/api/public-data/route.ts
export async function GET() {
  const data = await fetchPublicData()

  return NextResponse.json(data, {
    headers: {
      'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
      'CDN-Cache-Control': 'max-age=7200',
    },
  })
}
```

- **s-maxage**: CDN cache duration (overrides max-age for shared caches)
- **CDN-Cache-Control**: Cloudflare-specific directive

### Cache Key Configuration

Ensure cache keys include relevant parameters:

```ts
// BAD: Same cache for all users
fetch(`https://api.example.com/dashboard`)

// GOOD: User-specific cache key
fetch(`https://api.example.com/dashboard`, {
  headers: {
    'x-user-id': userId,
  },
  cache: 'no-store', // Don't cache user-specific data in CDN
})
```

### CDN Purging

```ts
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache'

export async function POST(request: Request) {
  const { path, tag } = await request.json()

  if (path) {
    revalidatePath(path) // Purge specific path
  }

  if (tag) {
    revalidateTag(tag) // Purge all fetches with this tag
  }

  return Response.json({ revalidated: true })
}
```

## Server-Side Caching

Application-level caching reduces database load and improves response times.

### Next.js React cache()

```ts
// lib/data.ts
import { cache } from 'react'

export const getUser = cache(async (id: string) => {
  // This function is memoized during a single request
  const user = await db.query('SELECT * FROM users WHERE id = ?', [id])
  return user
})

// Can be called multiple times in components without re-fetching
const user1 = await getUser('123')
const user2 = await getUser('123') // Returns memoized result
```

### Next.js unstable_cache

```ts
import { unstable_cache } from 'next/cache'

export const getCachedProducts = unstable_cache(
  async () => {
    return await db.query('SELECT * FROM products')
  },
  ['products-list'], // Cache key
  {
    revalidate: 3600, // Cache for 1 hour
    tags: ['products'], // For on-demand revalidation
  }
)
```

### Redis Caching (Python)

```python
import redis
import json
from functools import wraps

redis_client = redis.Redis(host='localhost', port=6379, db=0)

def cache_result(ttl: int = 300):
    """Decorator for caching function results in Redis"""
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            # Generate cache key from function name and arguments
            cache_key = f"{func.__name__}:{args}:{kwargs}"

            # Try to get from cache
            cached = redis_client.get(cache_key)
            if cached:
                return json.loads(cached)

            # Cache miss - execute function
            result = await func(*args, **kwargs)

            # Store in cache
            redis_client.setex(
                cache_key,
                ttl,
                json.dumps(result)
            )

         

Related in Backend & APIs