Caching Strategy
Rules
- Cache at every layer: browser (Cache-Control), CDN (edge), application (Redis), database (query cache)
- Use
stale-while-revalidate for content that can be slightly stale — serve fast, refresh in background
- Cache-Control headers:
public, max-age=31536000, immutable for hashed static assets; public, s-maxage=60, stale-while-revalidate=300 for dynamic pages
- Redis caching: serialize to JSON, set TTL on every key, use namespaced keys (
cache:users:123)
- React Query / TanStack Query: set
staleTime (how long data is fresh) and gcTime (how long to keep in memory)
- Invalidate on write — when data changes, delete or update the cache entry immediately
- Use cache-aside pattern: check cache first, fetch from source on miss, write to cache
- For Next.js: leverage ISR (
revalidate), unstable_cache, and fetch cache for server components
- Tag-based invalidation: tag cached entries by entity type, invalidate all entries for a tag on mutation
Patterns
async function getCachedUser(id: string) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await db.user.findUnique({ where: { id } });
await redis.set(`user:${id}`, JSON.stringify(user), "EX", 3600);
return user;
}
Avoid
- Caching without TTL — stale data will persist indefinitely
- Caching user-specific data in shared CDN cache — use
private or Vary headers
- Cache stampede: use locking or
stale-while-revalidate to prevent thundering herd
- Caching error responses — only cache successful results