Claude Code for Caching: Redis Patterns, CDN, and Application Cache Layers — Claude Skills 360 Blog
Blog / Development / Claude Code for Caching: Redis Patterns, CDN, and Application Cache Layers
Development

Claude Code for Caching: Redis Patterns, CDN, and Application Cache Layers

Published: August 6, 2026
Read time: 9 min read
By: Claude Skills 360

Caching is the highest-leverage performance optimization in most systems — a single cache hit saves database queries, computation, and network round trips. The hard parts are invalidation (knowing when cached data is stale), stampede prevention (preventing thousands of requests to rebuild the same cache entry), and layering (which data belongs at which cache level). Claude Code generates the cache patterns appropriate for each data access pattern.

Cache-Aside Pattern

Cache user profiles. Read from cache first, then DB on miss.
Invalidate on profile update. Handle race conditions.
// services/UserCache.ts
import { Redis } from 'ioredis';
import { db } from '../db';

const redis = new Redis(process.env.REDIS_URL!);
const TTL = 3600; // 1 hour

export class UserCache {
  private key(userId: string) { return `user:${userId}`; }
  private lockKey(userId: string) { return `lock:user:${userId}`; }

  async get(userId: string): Promise<User | null> {
    const cached = await redis.get(this.key(userId));
    if (cached) return JSON.parse(cached);

    // Cache stampede prevention: distributed lock
    const lockAcquired = await redis.set(
      this.lockKey(userId), '1', 'NX', 'EX', 10
    );

    if (!lockAcquired) {
      // Another process is fetching — wait briefly and retry from cache
      await new Promise(r => setTimeout(r, 50));
      const retried = await redis.get(this.key(userId));
      return retried ? JSON.parse(retried) : null;
    }

    try {
      const user = await db('users').where('id', userId).first();
      if (user) {
        await redis.setex(this.key(userId), TTL, JSON.stringify(user));
      }
      return user ?? null;
    } finally {
      await redis.del(this.lockKey(userId));
    }
  }

  async invalidate(userId: string): Promise<void> {
    await redis.del(this.key(userId));
  }

  async invalidateMany(userIds: string[]): Promise<void> {
    if (userIds.length === 0) return;
    await redis.del(...userIds.map(id => this.key(id)));
  }
}

Write-Through Pattern

Product inventory must always be fresh — cache and DB must stay in sync.
Write to both simultaneously on every update.
// services/InventoryCache.ts — write-through: always write cache + DB together
export class InventoryCache {
  async updateStock(productId: string, newStock: number): Promise<void> {
    // Write to both in parallel — if either fails, retry
    await Promise.all([
      db('products').where('id', productId).update({ stock: newStock }),
      redis.setex(`inventory:${productId}`, 300, newStock.toString()), // 5 min TTL
    ]);
  }

  async getStock(productId: string): Promise<number> {
    const cached = await redis.get(`inventory:${productId}`);
    if (cached !== null) return parseInt(cached);

    // Miss — load from DB and populate cache
    const product = await db('products').where('id', productId).first('stock');
    const stock = product?.stock ?? 0;
    await redis.setex(`inventory:${productId}`, 300, stock.toString());
    return stock;
  }
}

Batch Caching with Multi-Get

We're making 50 individual cache requests per page render.
Batch them into one round trip.
// services/ProductCache.ts — batch fetch with multi-get
export class ProductCache {
  async getMany(productIds: string[]): Promise<Map<string, Product>> {
    if (productIds.length === 0) return new Map();

    const keys = productIds.map(id => `product:${id}`);
    const cached = await redis.mget(...keys);

    const results = new Map<string, Product>();
    const missedIds: string[] = [];

    cached.forEach((value, index) => {
      if (value !== null) {
        results.set(productIds[index], JSON.parse(value));
      } else {
        missedIds.push(productIds[index]);
      }
    });

    if (missedIds.length > 0) {
      // Single DB query for all cache misses
      const products = await db('products').whereIn('id', missedIds);

      const pipeline = redis.pipeline();
      for (const product of products) {
        results.set(product.id, product);
        pipeline.setex(`product:${product.id}`, 3600, JSON.stringify(product));
      }
      await pipeline.exec();
    }

    return results;
  }
}

CDN Cache Configuration

Configure Cloudflare caching rules for our API.
Cache static assets forever. Cache API responses for 1 minute.
Never cache authenticated responses or checkout endpoints.
// Set cache headers in your API responses
export function setCacheHeaders(res: Response, options: {
  maxAge?: number;
  sMaxAge?: number;  // CDN TTL (overrides maxAge for CDN)
  private?: boolean;
  noStore?: boolean;
  staleWhileRevalidate?: number;
  tags?: string[];   // Cache tags for targeted purging (Cloudflare Cache-Tag)
}) {
  if (options.noStore) {
    res.setHeader('Cache-Control', 'no-store');
    return;
  }

  const directives: string[] = [];

  if (options.private) {
    directives.push('private');
  } else {
    directives.push('public');
  }

  if (options.maxAge !== undefined) {
    directives.push(`max-age=${options.maxAge}`);
  }

  if (options.sMaxAge !== undefined) {
    directives.push(`s-maxage=${options.sMaxAge}`);
  }

  if (options.staleWhileRevalidate !== undefined) {
    directives.push(`stale-while-revalidate=${options.staleWhileRevalidate}`);
  }

  res.setHeader('Cache-Control', directives.join(', '));

  // Cloudflare Cache-Tag for instant purging by tag
  if (options.tags?.length) {
    res.setHeader('Cache-Tag', options.tags.join(','));
  }
}

// Route-level cache configuration
app.get('/api/products', async (req, res) => {
  const products = await productCache.getMany(/* ... */);

  setCacheHeaders(res, {
    sMaxAge: 60,                    // CDN caches for 1 minute
    maxAge: 0,                      // Browsers don't cache
    staleWhileRevalidate: 300,      // Serve stale for 5 min while revalidating
    tags: ['products'],             // Purge tag when products change
  });

  res.json(products);
});

app.get('/api/products/:id', async (req, res) => {
  const product = await productCache.get(req.params.id);

  setCacheHeaders(res, {
    sMaxAge: 3600,
    maxAge: 60,
    tags: [`product:${req.params.id}`],  // Purge single product
  });

  res.json(product);
});

// Never cache authenticated routes
app.get('/api/cart', authenticate, (req, res) => {
  setCacheHeaders(res, { private: true, maxAge: 0 });
  // ...
});

Cache Invalidation Strategy

// After product update, purge CDN cache for affected URLs/tags
async function onProductUpdated(productId: string) {
  // 1. Invalidate application cache
  await redis.del(`product:${productId}`);

  // 2. Purge CDN cache by tag
  await fetch(`https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/cache/tags`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${CF_API_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ tags: [`product:${productId}`, 'products'] }),
  });

  // 3. Rebuild cache proactively (optional: prevents stampede on popular products)
  const product = await db('products').where('id', productId).first();
  await redis.setex(`product:${productId}`, 3600, JSON.stringify(product));
}

CLAUDE.md for Caching Decisions

## Caching Strategy
- User profiles: cache-aside, 1h TTL, invalidate on update
- Product catalog: write-through + CDN, 1h TTL, tag-based purge
- Inventory: write-through, 5min TTL (always fresh)
- Search results: cache-aside, 5min TTL, key = hash(query+filters)
- Auth tokens: Redis, TTL = token expiry, no extension
- Never cache: checkout, payment, user-specific financial data
- Stampede prevention: distributed lock pattern for hot keys
- Cache miss rate target: < 10% for product catalog, < 20% for search

For the Redis setup and infrastructure these patterns require, see the Redis guide. For CDN configuration beyond caching including DDoS protection, see the Cloudflare Workers guide. The Claude Skills 360 bundle includes caching skill sets for Redis patterns, CDN configuration, and cache strategy selection. Start with the free tier to try cache implementation generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free