Claude Code Caching Strategies for Real Apps
A practical Claude Code guide to HTTP cache, CDN, Service Worker Cache API, Redis, and cache invalidation.
Caching is not a magic switch for making an app fast. In a real product, HTTP headers, the CDN, a service worker, Redis, and process memory all store different things for different lifetimes. If you ask Claude Code to “add caching” without boundaries, the app may become faster while still showing old prices, stale inventory, or even user-specific data to the wrong person.
This guide gives you a practical cache strategy you can hand to Claude Code before it edits code. Masa tested these patterns in a small Express app and a content-site workflow; the biggest improvement came from deciding what each layer owns before adding Redis or a service worker.
flowchart LR
User[Browser] --> Http[HTTP cache]
Http --> SW[Service worker Cache API]
SW --> CDN[CDN or edge cache]
CDN --> App[Node or app server]
App --> Redis[Redis or app cache]
App --> DB[(Database)]
Start With Decisions
Before Claude Code writes implementation, answer four questions:
- Which users are allowed to see the same response?
- How stale can the data be before it becomes harmful?
- Which key, URL, or cache tag is deleted after an update?
- Who runs the rollback or purge when the cache is wrong?
Static images and hashed JavaScript bundles can usually live for a year. Billing pages, account settings, and authenticated HTML should not be stored in a shared cache. MDN’s HTTP caching guide explains the important difference between private browser caches and shared caches such as proxies or CDNs.
Layer Comparison
| Layer | Best for | TTL guide | Invalidation model | Common failure |
|---|---|---|---|---|
| Browser HTTP cache | Images, CSS, JS, short public API responses | 1 minute to 1 year | Filename changes, ETag, Cache-Control | Long-lived API responses show old screens |
| CDN or edge cache | Product lists, article HTML, OGP images | 30 seconds to 1 day | URL purge, tag purge, deploy purge | Authenticated HTML is cached for everyone |
| Service Worker Cache API | Offline page, app shell, rarely changed JSON | Version based | Change cache name on release | Old worker keeps serving old bundles |
| Redis or app cache | DB aggregates, external API results, rankings | 10 seconds to 1 hour | Key design, update events, TTL | Production Redis is blocked by KEYS |
| Process memory | Config values, short feature flag copies | Seconds to minutes | Restart or explicit clear | Multiple app instances disagree |
Put this table in CLAUDE.md so Claude Code has a stable rulebook when adding a new route. For more project instructions, see CLAUDE.md best practices.
Use Case 1: HTTP Cache in Express
The first cache to fix is often not Redis. It is the response header. Cache-Control tells browsers and CDNs how long a response may be stored. The exact directives are documented in MDN’s Cache-Control header.
Copy this as server.js and run it.
npm install express
node server.js
// server.js
const express = require("express");
const app = express();
function cacheControl(req, res, next) {
const path = req.path;
if (path.startsWith("/assets/")) {
res.set("Cache-Control", "public, max-age=31536000, immutable");
return next();
}
if (path.startsWith("/api/private/")) {
res.set("Cache-Control", "no-store");
return next();
}
if (path.startsWith("/api/public/")) {
res.set("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=600");
res.set("Vary", "Accept-Encoding");
return next();
}
res.set("Cache-Control", "no-cache");
next();
}
app.use(cacheControl);
app.use("/assets", express.static("public/assets"));
app.get("/api/public/products", (_req, res) => {
res.json({ items: ["book", "template", "consultation"], generatedAt: new Date().toISOString() });
});
app.get("/api/private/me", (_req, res) => {
res.json({ userId: "demo-user", plan: "team" });
});
app.listen(3000, () => {
console.log("http://localhost:3000");
});
The important design choice is route separation. /api/private/ gets no-store, so neither the browser nor a CDN should store it. Public API responses get a short browser TTL and a longer CDN TTL through s-maxage.
When you prompt Claude Code, write the rule explicitly: “Authenticated responses must use no-store; only public API routes may use s-maxage.”
Use Case 2: Redis getOrSet Helper
Redis is useful when database queries or external APIs are repeated often. The safe default is the cache-aside pattern: check Redis first, load from the source on a miss, then store the value with a TTL.
npm install redis
// cache.js
const { createClient } = require("redis");
const redis = createClient({
url: process.env.REDIS_URL || "redis://localhost:6379",
});
let connecting;
async function client() {
if (redis.isOpen) return redis;
if (!connecting) connecting = redis.connect();
await connecting;
return redis;
}
async function getOrSet(key, ttlSeconds, loader) {
const r = await client();
const cached = await r.get(key);
if (cached !== null) {
return JSON.parse(cached);
}
const fresh = await loader();
await r.set(key, JSON.stringify(fresh), { EX: ttlSeconds });
return fresh;
}
async function invalidate(keys) {
const r = await client();
if (keys.length > 0) {
await r.del(keys);
}
}
module.exports = { getOrSet, invalidate };
// products.js
const { getOrSet, invalidate } = require("./cache");
async function loadProductsFromDb() {
return [
{ id: "p1", name: "Prompt Templates", price: 500 },
{ id: "p2", name: "Claude Code Consultation", price: 15000 },
];
}
async function getPublicProducts() {
return getOrSet("products:list:v1", 300, loadProductsFromDb);
}
async function updateProduct(productId, patch) {
console.log("update db", productId, patch);
await invalidate(["products:list:v1", `products:item:${productId}:v1`]);
}
module.exports = { getPublicProducts, updateProduct };
Avoid KEYS products:* in production. As the keyspace grows, it can block Redis. Prefer known key lists, sets of related keys, or a SCAN based maintenance command.
Use Case 3: Service Worker Cache Versioning
A service worker can intercept browser requests. The related Cache API is useful for offline pages, app shells, and static assets. The danger is stale worker code: if the cache name never changes, an old worker may keep serving old JavaScript after a deploy.
// public/sw.js
const CACHE_VERSION = "claude-code-cache-v2026-06-01";
const STATIC_CACHE = `${CACHE_VERSION}:static`;
const PRECACHE_URLS = ["/", "/offline.html", "/assets/app.css"];
self.addEventListener("install", (event) => {
event.waitUntil(
caches
.open(STATIC_CACHE)
.then((cache) => cache.addAll(PRECACHE_URLS))
.then(() => self.skipWaiting())
);
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches
.keys()
.then((names) =>
Promise.all(
names
.filter((name) => !name.startsWith(CACHE_VERSION))
.map((name) => caches.delete(name))
)
)
.then(() => self.clients.claim())
);
});
self.addEventListener("fetch", (event) => {
const request = event.request;
if (request.method !== "GET") return;
event.respondWith(
caches.match(request).then((cached) => {
if (cached) return cached;
return fetch(request)
.then((response) => {
if (response.ok && new URL(request.url).pathname.startsWith("/assets/")) {
const copy = response.clone();
caches.open(STATIC_CACHE).then((cache) => cache.put(request, copy));
}
return response;
})
.catch(() => caches.match("/offline.html"));
})
);
});
Register it from your client entry.
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/sw.js");
});
}
Tell Claude Code that the cache name must include a release date or build ID, and that old caches must be deleted in activate.
Use Case 4: Claude Code Cache Audit Prompt
Claude Code is especially useful when it audits a whole repository instead of adding a single helper. The official Claude Code common workflows encourage small investigation, edit, and verification loops; that fits cache work well.
You are auditing cache behavior in a web application.
Inspect this repository for HTTP headers, CDN assumptions, service workers, Redis, and process-memory caches.
Return:
1. Data stored by each cache layer
2. TTL and invalidation condition
3. Risk that personal or authenticated responses reach a shared cache
4. Concrete screens where stale data can appear
5. Minimal fix plan and verification commands
Constraints:
- Mark assumptions clearly
- Follow existing project patterns
- Suggest large refactors separately instead of applying them immediately
This prompt acts as reusable context. Instead of re-explaining your cache policy every time, keep the audit checklist in a template or CLAUDE.md and only pass the current diff.
Practical Scenarios
For a product catalog or template store, public product cards can use a CDN for a short time while the database list is cached in Redis for five minutes. After an edit, invalidate the product-list key and purge the affected CDN URL.
For an admin dashboard, revenue and page-view aggregates can be cached for 30 seconds to 5 minutes. Permissions, personal notifications, and billing data should be private or no-store.
For docs and blogs, article HTML can sit briefly at the edge, hashed assets can be long lived, and the offline shell can be owned by the service worker. This is the pattern that fits content-heavy sites like ClaudeCodeLab.
For external APIs, use Redis to absorb rate limits for data such as weather, plans, exchange rates, or status feeds. Ask Claude Code to check the API terms before caching, because some providers restrict storage.
Pitfalls
The most serious mistake is caching authenticated HTML in a CDN. If a response depends on cookies, default to private or no-store. If you need CDN speed, split the page so the shared shell is public and user-specific data is fetched later.
Missing Vary headers are another common cause of wrong responses. If language, compression, device type, or authorization changes the response, either separate the URL or send the right Vary header.
Redis can suffer from a cache stampede when a popular key expires and many requests hit the database at once. Add random TTL jitter, a short lock, or a stale-while-revalidate style fallback.
Service workers can keep deleted files alive. Version the cache, remove old names during activate, and keep an emergency procedure for unregistering the worker if a bad release reaches users.
Invalidation Runbook
- Define the blast radius: product, article, user, or whole app.
- Finish the database write first; do not delete cache if the write fails.
- Delete Redis keys using known keys, related-key sets, or
SCAN. - Purge CDN by URL or tag. Full purge is the last resort.
- Bump the service worker cache version and confirm old caches disappear.
- Verify with
curl -I, browser DevTools, and Redis hit-rate logs. - During an incident, shorten TTLs or move sensitive routes to
no-store, then restore gradually.
Make this runbook part of Claude Code’s definition of done. For broader workflow discipline, pair it with Claude Code productivity tips.
Training, Templates, and Consultation
If you want this turned into a repeatable team workflow, start with the ClaudeCodeLab product and template library so your CLAUDE.md, review prompts, and audit prompts are consistent. If the hard part is mapping CDN, Redis, permissions, and review rules onto a real repository, use the Claude Code training and consultation page.
For official references, read MDN on HTTP caching, the Cache API, Cache-Control, and Anthropic’s Claude Code common workflows.
After trying the examples in this article, Masa found that static asset requests dropped, public API responses became more predictable with CDN-oriented TTLs, and the Redis getOrSet helper made unnecessary database reads visible in logs. The service worker also exposed the main lesson: without versioned deletion, stale CSS survives a deploy. The practical win is not just speed; it is writing down where each cached value is allowed to become old.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Permission Receipt Pattern: Record Scope, Proof, and Rollback
A permission receipt pattern for Claude Code: allowed actions, approval boundaries, proof commands, rollback, and revenue CTA checks.
Safe Agent Harness Design for Claude Code and Codex: Permissions, Checks, and Rollback
Build a practical agent harness for Claude Code and Codex with policy, planning, verification, and recovery layers.
Claude Code Subagents: A Practical Guide to Safe Agent Delegation
Claude Code subagent guide for safe parallel article and code work: delegation rules, prompts, pitfalls, and checks.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.