Caching Strategies: Browser, CDN, and Application Cache Explained
Caching is one of the highest-leverage performance optimizations available. Done well, it can reduce server load by 80%, cut response times from hundreds of milliseconds to single-digit milliseconds, and dramatically improve user experience. Done poorly, it serves stale data and causes hard-to-debug bugs. This guide explains each caching layer and how to use them correctly.
The Three Caching Layers
Every web request passes through multiple potential caches before reaching your database:
- 1Browser cache — stored on the user's device
- 2CDN / edge cache — stored on servers close to the user, globally distributed
- 3Application cache — stored in Redis or similar, in your infrastructure
Each layer serves a different purpose and requires different configuration.
Browser Caching
Browsers cache responses based on HTTP headers you control. The two most important headers are Cache-Control and ETag.
# Cache static assets for 1 year (they should be content-hashed)
Cache-Control: public, max-age=31536000, immutable
# Cache HTML for a short time or not at all
Cache-Control: no-cache
# Validate with ETag before using cached version
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d"Content-hashed filenames are the key technique here. When you build main.a3f2b1.js instead of main.js, the filename changes every time the content changes. This lets you cache the file for a year safely — the URL itself becomes the cache-busting mechanism.
// Vite / webpack generates content hashes automatically
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash].[ext]',
}
}
}
}CDN Caching
A CDN (Content Delivery Network) caches your content at edge nodes distributed globally. When a user in Singapore requests your site hosted in the US, the CDN serves cached content from an edge node nearby — eliminating transcontinental round-trip latency.
CDNs respect the same Cache-Control headers as browsers, but you can add s-maxage to differentiate CDN TTL from browser TTL:
# Cache for 5 minutes in CDN, but let browser re-validate every request
Cache-Control: public, s-maxage=300, max-age=0, must-revalidateFor API responses that rarely change, CDN caching can be transformative — serving thousands of requests per second from edge without touching your origin.
Application-Level Caching with Redis
Application caching is for expensive operations: database queries, external API calls, computation-heavy aggregations. Redis is the standard tool.
Cache-aside pattern (most common):
async function getUserDashboard(userId) {
const cacheKey = `dashboard:${userId}`;
// 1. Check cache
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
// 2. Cache miss — fetch from database
const data = await db.query(
'SELECT * FROM dashboard_metrics WHERE user_id = $1',
[userId]
);
// 3. Store in cache with 5-minute TTL
await redis.setex(cacheKey, 300, JSON.stringify(data));
return data;
}Cache invalidation — the hard part. When data changes, you must either invalidate the relevant cache keys or accept that data will be stale until the TTL expires. Two strategies:
- TTL-based: accept short-lived staleness. Simple, works well for dashboards and reports.
- Event-based: delete cache keys explicitly when data changes. More complex, always consistent.
// Event-based invalidation on update
async function updateUserProfile(userId, data) {
await db.query('UPDATE users SET ... WHERE id = $1', [userId]);
await redis.del(`dashboard:${userId}`); // Invalidate cache immediately
}Choosing TTL Values
| Data Type | Suggested TTL |
|---|---|
| User session | 30 minutes (sliding) |
| Dashboard metrics | 60–300 seconds |
| Product catalog | 10–60 minutes |
| Static content | 1 year (content-hashed) |
| Real-time data | 0 (do not cache) |
What Not to Cache
- Personalized, user-specific API responses via CDN — without careful Vary headers, one user's data will be served to another
- Mutation results — POST/PUT/DELETE responses should not be cached
- Payment or authentication responses — never cache sensitive data
PandaStack and Redis
[PandaStack](https://dashboard.pandastack.io) supports Redis as a managed database alongside PostgreSQL, MySQL, and MongoDB. This means you can provision a Redis instance alongside your application container with no additional infrastructure setup, then use it for session storage, cache-aside caching, and pub/sub — all from the same dashboard.
Summary
Layer your caching strategy:
- 1Browser: long TTLs on content-hashed static assets
- 2CDN: cache public API responses and pages at the edge
- 3Redis: cache expensive database queries and computations
Each layer catches different traffic patterns. Together, they can handle 10× the load of an uncached system with a fraction of the infrastructure cost.