DEV Community

丁久
丁久

Posted on • Originally published at dingjiu1989-hue.github.io

Caching Strategies for Web Apps: CDN, Redis, Browser, and API Caching

This article was originally published on AI Study Room. For the full version with working code examples and related articles, visit the original post.

Caching Strategies for Web Apps: CDN, Redis, Browser, and API Caching

Caching is the difference between a 50ms response and a 5-second timeout. But cache invalidation is famously one of the hardest problems in computer science. Here's a practical guide to caching at every layer — and when NOT to cache.

The Caching Layers

Layer What to Cache TTL Invalidation
Browser (HTTP Cache) Static assets (JS, CSS, images, fonts) 1 year (with hash in filename) Change filename → new URL → cache miss
CDN HTML, API responses, images 1 min to 1 hour Purge by URL or tag. Stale-while-revalidate.
Application (Redis/Memcached) DB query results, computed values, sessions 1 second to 1 hour Delete on write. TTL-based. Cache-aside pattern.
Database query cache Query results (PostgreSQL/MySQL built-in) Automatic Invalidated on table writes.
Next.js data cache fetch() results in Server Components Configurable revalidateTag(), revalidatePath()

1. Browser & CDN: Cache-Control Headers

# Static assets with content hash (1 year)

/_next/static/chunks/main-abc123.js

Cache-Control: public, max-age=31536000, immutable

HTML pages (revalidate at CDN, serve stale if origin is down)

/blog/my-post

Cache-Control: public, s-maxage=60, stale-while-revalidate=300

API responses that don't change often

/api/posts/trending

Cache-Control: public, max-age=300, s-maxage=300

Never cache (user-specific data)

/api/user/profile

Cache-Control: private, no-cache, no-store, must-revalidate

Enter fullscreen mode Exit fullscreen mode



  1. Application Cache: Redis

// Cache-aside pattern — the most common approach
async function getUserPosts(userId: string): Promise<Post[]> {
const cacheKey = user:${userId}:posts;


// 1. Try cache
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);

// 2. Cache miss — fetch from DB
const posts = await db.posts.findMany({ where: { userId } });

// 3. Store in cache (5 minutes)
await redis.set(cacheKey, JSON.stringify(posts), "EX", 300);

return posts;
}

// Delete cache on write — prevent stale data
async function createPost(userId: string, data: CreatePostInput) {
const post = await db.posts.create({ data: { userId, ...data } });
await redis.del(user:${userId}:posts); // Invalidate
return post;
}

Enter fullscreen mode Exit fullscreen mode



  1. Next.js Caching (App Router)

// Static data — cached permanently
async function getNavigation() {
const res = await fetch("https://cms.example.com/navigation");
return res.json(); // Cached forever (build-time)
}


// Revalidated data — cached, then refreshed
async function getBlogPosts() {
const res = await fetch("https://cms.example.com/posts", {
next: { revalidate: 3600 }, // Revalidate every hour
});
return res.json();
}

// On-demand revalidation (webhook from CMS)
import { revalidateTag } from "next/cache";

export async function POST(request: Request) {
const { tag } = await request.json();
revalidateTag(tag); // Revalidate everything with this tag
return Response.json({ revalidated: true });
}

Enter fullscreen mode Exit fullscreen mode




When NOT to Cache

  • User-specific data that changes frequently: Shopping cart, notifications, real-time dashboards.
  • Write-heavy data: If the data changes every second, caching just adds complexity.
  • Data that must be accurate: Bank balances, inventory counts during flash sales. Use the database directly or use a cache with write-through.
  • Before you have a performance problem: Caching prematurely adds complexity. Wait until you measure a bottleneck.

Cache Invalidation Strategies

Strategy How When
TTL (Time to Live) Set expiry. Data is stale for up to TTL. When staleness is acceptable (analytics, trending, recommendations)
Write-through Write to cache AND DB simultaneously. When you need consistency and read latency matters
Cache-aside (lazy) Read from cache, fall back to DB. Delete on write. Most common. Good balance of simplicity and freshness.
Stale-while-revalidate Serve stale, refresh in background. CDN. Tolerates staleness for a few seconds for massive latency wins.

Bottom line: Cache at the CDN first (biggest win, simplest). Add Redis when you have specific slow queries. Use Next.js built-in caching for data fetching. Invalidate on write, not on a timer, for user-facing data. See also: Web Performance Tools and Database Comparison.


Read the full article on AI Study Room for complete code examples, comparison tables, and related resources.

Found this useful? Check out more developer guides and tool comparisons on AI Study Room.

Top comments (0)