Caching strategies — invalidation, TTL guidelines, cache keys, cache layers, and when not to cache. Use when implementing or reviewing caching logic.
73
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
staleTime/gcTime for client-side caching.<entity>:<id>:<variant> (e.g., user:123:profile, products:list:page=2).| Layer | Best For | TTL Range |
|---|---|---|
| In-memory (Map, LRU) | Hot data, single-instance apps | Seconds to minutes |
| Redis / Memcached | Shared cache across instances, sessions | Minutes to hours |
| CDN / Edge | Static assets, public API responses | Hours to days |
| HTTP cache headers | Browser caching, API responses | Varies by resource |
const cache = new Map<string, { value: unknown; expires: number }>();
const MAX_SIZE = 500;
export function getOrSet<T>(key: string, ttlMs: number, compute: () => T): T {
const entry = cache.get(key);
if (entry && entry.expires > Date.now()) return entry.value as T;
const value = compute();
if (cache.size >= MAX_SIZE) {
// Evict oldest entry (first inserted)
const oldest = cache.keys().next().value!;
cache.delete(oldest);
}
cache.set(key, { value, expires: Date.now() + ttlMs });
return value;
}import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
export async function swr<T>(
key: string,
freshSec: number,
staleSec: number,
fetcher: () => Promise<T>,
): Promise<T> {
const raw = await redis.get(key);
if (raw) {
const { value, createdAt } = JSON.parse(raw) as { value: T; createdAt: number };
const ageMs = Date.now() - createdAt;
if (ageMs < freshSec * 1000) return value; // Fresh — return immediately
if (ageMs < staleSec * 1000) {
// Stale — return cached, refresh in background
fetcher().then((v) =>
redis.set(key, JSON.stringify({ value: v, createdAt: Date.now() }), "EX", staleSec),
);
return value;
}
}
const value = await fetcher();
await redis.set(key, JSON.stringify({ value, createdAt: Date.now() }), "EX", staleSec);
return value;
}// Immutable assets (hashed filenames)
app.use("/assets", (_, res, next) => {
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
next();
});
// API responses — short cache with revalidation
app.get("/api/products", (_, res) => {
res.setHeader("Cache-Control", "public, max-age=60, stale-while-revalidate=300");
res.json(products);
});import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // Data fresh for 5 minutes
gcTime: 30 * 60 * 1000, // Garbage-collect after 30 minutes
retry: 2,
refetchOnWindowFocus: false,
},
},
});
// Usage in a component
const { data } = useQuery({
queryKey: ["products", { page, category }], // Cache key includes params
queryFn: () => fetchProducts({ page, category }),
});Cache-and-forget — Caching data with no invalidation strategy. Data goes stale permanently.
Uniform TTL — Using the same TTL (e.g., 1 hour) for all data regardless of volatility.
Missing key parameters — Cache key omits user ID, locale, or query params, serving wrong data.
products:list:page=2:locale=en.Caching errors — Storing error responses (500s, timeouts) with long TTLs.
Cache stampede — All instances hit the origin simultaneously when a popular key expires.
af7d491
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.