Caching stores the result of an expensive operation so you can return it instantly the next time it's needed — without repeating the work.
Slow database queries, expensive API calls, heavy computations — all are candidates for caching. The trade-off is always the same: freshness vs. speed. A cache returns slightly older data instantly; a fresh fetch returns current data slowly.
If Cartara flagged this in your diff, you likely added Redis calls, cache headers, or logic that checks a cache before hitting the database.
Where Caching Happens
Modern apps typically cache at multiple layers:
Browser cache ← user's own machine; fastest for repeat visits
↓
CDN cache ← geographically close servers; fast for everyone
↓
Application cache ← Redis or in-memory; fast for database-heavy ops
↓
The actual databaseA request served by a higher layer is faster, cheaper, and puts less load on everything below it.
Browser Caching
The browser stores assets (HTML, CSS, JS, images) locally so they don't need to be re-downloaded on repeat visits.
Controlled via HTTP response headers:
Cache-Control: max-age=31536000— cache for one year (use for assets with content-hashed filenames likeapp.abc123.js)Cache-Control: no-cache— always revalidate before using the cached versionCache-Control: no-store— never cache (for sensitive data)
Most hosting platforms (Vercel, Netlify, Cloudflare) set sensible cache headers automatically for static assets.
CDN Caching
A Content Delivery Network (CDN) caches content at servers geographically close to users. A visitor in Tokyo gets served from a Tokyo node rather than a server in Virginia — dramatically reducing latency.
CDNs are most valuable for static assets (JS, CSS, images) and public API responses. For dynamic, user-specific data, CDN caching is harder to use correctly.
Providers: Cloudflare (excellent free tier), Vercel Edge Network (automatic for Vercel apps), AWS CloudFront.
Application Caching with Redis
Redis is the standard tool for application-level caching. It's an in-memory data store — fast (sub-millisecond), shared across all your app servers, and persistent across restarts.
The basic pattern — check the cache first, fall back to the database on a miss:
async function getUserProfile(userId: string) {
// Check cache first
const cached = await redis.get(`user:${userId}:profile`);
if (cached) return JSON.parse(cached); // cache hit — instant
// Cache miss — fetch from database
const user = await db.users.findById(userId);
// Store in cache for 5 minutes
await redis.setex(`user:${userId}:profile`, 300, JSON.stringify(user));
return user;
}Common Redis use cases:
- User profile and preference caching
- Session storage
- API response caching
- Rate limiting counters
- Job queues (via BullMQ)
Managed Redis options: Upstash (serverless, great for small apps), Redis Cloud, Railway Redis.
What You'll See in Your Code
Caching code typically appears in diffs as:
A cache-check pattern before a database call (as shown above).
Cache invalidation when data is updated:
async function updateUserProfile(userId: string, data: UpdateData) {
await db.users.update(userId, data);
await redis.del(`user:${userId}:profile`); // bust the cache
}HTTP cache headers on API responses:
res.setHeader('Cache-Control', 'public, max-age=60'); // cache for 60 secondsA Redis client setup file (lib/redis.ts or similar):
import { Redis } from '@upstash/redis';
export const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL!,
token: process.env.UPSTASH_REDIS_TOKEN!,
});Cache Invalidation
"There are only two hard things in Computer Science: cache invalidation and naming things."
Knowing when to expire or refresh cached data is the hard part. Two main strategies:
TTL (Time-To-Live) — data automatically expires after a set period. Simple to implement. Choose TTLs based on how often the underlying data changes and how stale is acceptable:
- User sessions: hours or days
- Product prices: minutes to hours
- Static content: days or indefinitely (with content-hashed filenames)
Event-driven invalidation — when data changes, explicitly delete or update the relevant cache entry. More current data, but more complex to implement correctly.
What Not to Cache
- User-specific sensitive data — a bug in cache key design that returns one user's data to another is a serious incident. Always include user ID in cache keys for user-specific data.
- Financial transactions — consistency is critical; stale data causes real harm.
- Security tokens — needs careful TTL management.