Advanced (更新: 2026/6/1)

Claude Code 实战缓存策略:HTTP、CDN、Service Worker 与 Redis

用 Claude Code 为真实应用设计 HTTP、CDN、Service Worker、Redis 缓存与失效流程。

Claude Code 实战缓存策略:HTTP、CDN、Service Worker 与 Redis

缓存不是让应用变快的魔法开关。在真实产品里,HTTP 头、CDN、Service Worker、Redis 和进程内内存都可能保存数据,而且保存位置、过期时间和风险完全不同。如果只对 Claude Code 说“加缓存”,它可能让页面更快,却留下旧价格、错误库存、甚至把用户私有信息放进共享缓存。

这篇文章给你一套可以直接交给 Claude Code 的缓存设计思路。Masa 在一个小型 Express 应用和内容站点工作流里验证过:真正有效的不是先上复杂工具,而是先决定“哪一层保存什么、能旧多久、如何删除”。

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)]

先做设计决定

让 Claude Code 改代码前,先回答四个问题:

  1. 哪些用户可以看到同一份响应?
  2. 数据最多可以旧多久?
  3. 更新后要删除哪个 key、URL 或缓存标签?
  4. 缓存出错时由谁按什么顺序回滚?

带 hash 的 JavaScript、CSS 和图片通常可以缓存很久。账单、个人设置、登录后的 HTML 则不能放进共享缓存。MDN 的 HTTP caching 解释了 private cache 和 shared cache 的区别,这是缓存安全的基本前提。

缓存层对比

适合的数据TTL 建议失效方式常见失败
浏览器 HTTP 缓存图片、CSS、JS、短期公开 API1 分钟到 1 年文件名变更、ETagCache-ControlAPI 被长缓存导致页面过旧
CDN 或边缘缓存商品列表、文章 HTML、OGP 图片30 秒到 1 天按 URL、标签或部署 purge登录后 HTML 被所有人共享
Service Worker Cache API离线页、应用外壳、低频 JSON按版本发布时改缓存名旧 worker 继续发旧资源
Redis 或应用缓存DB 聚合、外部 API、排行榜10 秒到 1 小时key 设计、更新事件、TTL在生产 Redis 上跑 KEYS
进程内内存配置、短期 feature flag 副本几秒到几分钟重启或显式 clear多实例之间数据不一致

建议把这张表写进 CLAUDE.md,这样 Claude Code 新增路由时也有统一判断。项目规则的写法可以参考 CLAUDE.md best practices

用例1:Express 的 HTTP 缓存头

最先修的往往不是 Redis,而是响应头。Cache-Control 告诉浏览器和 CDN 响应可以保存多久。各个指令可以看 MDN 的 Cache-Control header

复制为 server.js 后即可运行。

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");
});

这里的关键是把公开数据和私有数据放在不同 URL 下。/api/private/ 使用 no-store,浏览器和 CDN 都不应该保存。公开 API 使用短的 max-age,同时通过 s-maxage 让 CDN 保存稍久一点。

给 Claude Code 的指令要具体:认证后的响应必须使用 no-store,只有公开 API 才能使用 s-maxage

用例2:Redis getOrSet 辅助函数

Redis 适合降低数据库查询和外部 API 的重复调用。安全的默认模式是 cache-aside:先查 Redis,未命中时从源数据读取,再按 TTL 写入 Redis。

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 };

生产环境不要使用 KEYS products:*。key 增多后它可能阻塞 Redis。更稳妥的做法是保存已知 key 列表、用 Redis Set 记录关联 key,或写基于 SCAN 的维护命令。

用例3:Service Worker Cache API 版本管理

Service Worker 可以在浏览器里拦截请求。配套的 Cache API 适合保存离线页、应用外壳和静态资源。风险是旧 worker 代码:如果缓存名不变,部署后用户仍可能拿到旧 JavaScript。

// 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"));
    })
  );
});

客户端入口注册:

if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker.register("/sw.js");
  });
}

每次发布都要改变 CACHE_VERSION,并在 activate 阶段删除旧缓存。把这个要求写进 Claude Code 的任务说明里。

用例4:给 Claude Code 的缓存审计 prompt

Claude Code 的价值不只是写一个 helper,而是能横向检查整个仓库。官方 Claude Code common workflows 推荐用小步调查、编辑、验证的方式推进,缓存改造也适合这样做。

你是 Web 应用的缓存审计负责人。
请检查这个仓库中 HTTP 头、CDN 假设、Service Worker、Redis、进程内内存缓存的使用位置。

请输出:
1. 每一层缓存的数据
2. TTL 和失效条件
3. 个人信息或认证响应进入共享缓存的风险
4. 可能出现 stale data 的具体页面
5. 最小修复方案和验证命令

限制:
- 推测必须标注为推测
- 遵守现有项目风格
- 大重构只提出建议,不要直接执行

这个 prompt 本身也是可复用的上下文缓存。不要每次重新解释缓存规则,把审计清单放进模板或 CLAUDE.md,只传当前差异即可。

真实场景

商品目录或模板商店中,公开商品卡片可以短时间放在 CDN,数据库列表放在 Redis 五分钟。编辑商品后删除商品列表 key,并 purge 对应 CDN URL。

管理后台中,销售额、PV、转化率可以缓存 30 秒到 5 分钟。权限、个人通知、账单数据必须使用 privateno-store

文档和博客中,文章 HTML 可以短时间放在边缘节点,带 hash 的静态资源可以长期缓存,离线外壳由 Service Worker 管理。内容量大的 ClaudeCodeLab 类站点很适合这种分层。

外部 API 中,天气、汇率、SaaS 套餐状态等可以用 Redis 吸收频率限制。但要让 Claude Code 检查 API 条款,因为有些服务不允许缓存。

常见陷阱

最危险的是把登录后的 HTML 缓存在 CDN。只要响应依赖 Cookie,就默认使用 privateno-store。如果需要 CDN 速度,把公共外壳和用户专属数据拆开。

第二个问题是缺少 Vary。语言、压缩、设备或授权状态会改变响应时,要么拆 URL,要么发送正确的 Vary

Redis 还会遇到缓存击穿或雪崩。热门 key 同时过期时,大量请求会一起打到数据库。可以加入随机 TTL、短锁,或用 stale-while-revalidate 思路短暂返回旧值。

Service Worker 会让已删除文件继续存活。必须版本化缓存名,在 activate 删除旧缓存,并准备紧急注销 worker 的流程。

失效 runbook

  1. 明确影响范围:商品、文章、用户,还是全站。
  2. 先完成数据库写入,失败时不要删除缓存。
  3. 使用已知 key、关联 key 集合或 SCAN 删除 Redis。
  4. 按 URL 或标签 purge CDN,全量 purge 作为最后手段。
  5. 提升 Service Worker 缓存版本,确认旧缓存被删除。
  6. curl -I、浏览器 DevTools 和 Redis hit rate 验证。
  7. 事故期间先缩短 TTL 或把敏感路由切到 no-store,再逐步恢复。

把这个 runbook 作为 Claude Code 的完成条件。团队流程还可以结合 Claude Code productivity tips

ClaudeCodeLab 教材与咨询

如果想把这套策略变成团队可复用流程,可以先看 ClaudeCodeLab 产品和模板,统一 CLAUDE.md、review prompt 和审计 prompt。如果困难点是真实仓库里的 CDN、Redis、权限和 review 规则设计,可以使用 Claude Code 培训与咨询

官方资料建议收藏:MDN 的 HTTP cachingCache APICache-Control,以及 Anthropic 的 Claude Code common workflows

实际试用本文内容后,Masa 发现静态资源重复请求减少,公开 API 在 CDN TTL 下更稳定,Redis 的 getOrSet 让多余 DB 读取更容易从日志中看见。Service Worker 也暴露出最重要的教训:不删除旧版本,旧 CSS 会留在用户浏览器里。缓存的核心收益不只是速度,而是清楚写下每个值允许在哪里变旧。

#Claude Code #缓存 #Redis #CDN #Service Worker
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。