尧图精选

缓存策略三层模型实战:CDN、Service Worker 与 HTTP 缓存如何协同工作

🕒 发布时间:2026/10/2 20:13:19 📁 来源:尧图网络
1. 三层缓存到底拦住了什么从一次文档页请求说起前端性能优化里缓存策略三层模型指的是 CDN 边缘缓存、Service Worker 离线缓存、HTTP 缓存头这三层从远到近的拦截体系。它能做到的是让同一个 URL 的重复请求尽量不落到源站让回访用户几乎瞬间打开页面让弱网甚至离线状态下应用壳还能渲染。适合谁适合正在做中大型前端项目、被首屏和回源成本折磨、又不想把缓存配成“玄学”的工程师。我拿一个真实场景拆用户第一次访问/docs/getting-started。请求先到最近的 CDN 边缘节点节点本地没有这份 HTML于是回源源站渲染后返回同时带上Cache-Control边缘节点把响应存下来。浏览器拿到 HTML 后开始解析发现要加载main.a3f2b1c.js、字体、图片这些静态资源同样先过 CDN。此时 Service Worker 还没接管页面所以这一轮全靠 CDN 和 HTTP 缓存。用户点进第二个文档页Service Worker 已经安装并激活它开始拦截后续所有请求。对预缓存过的 JS/CSS/字体直接返回 CacheStorage 里的副本一个字节都不走网络对/api/请求按 NetworkFirst 策略先试网络超时才回退缓存对/docs/文档走 StaleWhileRevalidate先给缓存副本保证秒开后台再悄悄刷新。第三次访问时浏览器 HTTP 缓存也生效了。哈希化的main.a3f2b1c.js因为带了immutable浏览器连条件请求都不发直接读磁盘。三层各司其职CDN 拦跨用户的重复回源Service Worker 拦同一用户跨页面的重复请求HTTP 缓存拦同一会话内的重复读取。关键认知是这三层不是替代关系而是覆盖不同时间尺度和不同用户范围。CDN 面向“全球所有用户”Service Worker 面向“这台设备这个浏览器”HTTP 缓存面向“这个标签页这次会话”。任何一层配错都会出现“明明改了代码用户还是看到旧页面”或者“缓存命中率上不去回源爆炸”的问题。下面按可复制的顺序把三层逐个配到位。2. 接入前的准备用 TaoToken 统一管理模型调用与 Key在动手配缓存之前先把一个容易被忽略的前置问题解决掉很多前端项目里会内嵌 AI 能力比如文档页的智能问答、代码示例解释、错误信息翻译。这些接口如果直接写死在代码里Key 泄露和额度失控是迟早的事。我习惯用 TaoToken 把模型调用统一收口前端只请求自己的后端后端再通过 TaoToken 的 API 转发。TaoToken 是一个模型调用聚合平台官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。它的作用是让你用一套 Base URL 和 Key就能调用多家模型省去为每个模型单独申请、单独配环境的麻烦。对前端缓存场景来说它的价值在于文档页的 AI 问答接口可以走统一的/api/ai路由而这个路由的响应正好可以用 NetworkFirst 策略缓存避免用户重复问同一个问题时反复打模型。你需要准备三样东西我把它叫“三件套”Base URL、API Key、Model ID。Base URL 填https://taotoken.net/apiAPI Key 在控制台的 API Keys 页面生成Model ID 按你实际要用的模型填。这三件套在后面的配置里会反复出现尤其是当你想把 AI 接口也纳入缓存体系时。生成 Key 的入口在 https://taotoken.net/console/api-keys 文档在 https://taotoken.net/doc 。如果你只是想让文档页有个能用的问答用模型对话页面先验证一下模型是否通 https://taotoken.net/models 。如果项目里还要跑长期编码任务或者 Agent可以看 Coding Plan https://taotoken.net/coding-plan 。这里要强调一点Key 绝对不能写进前端代码。正确做法是前端请求你自己的/api/ai后端用环境变量存 Key再转发到 TaoToken。这样即使前端被扒泄露的也只是你自己的接口而不是模型额度。下面进入配置环节我会把 AI 接口的缓存策略也一并写进去。3. 可复制配置HTTP 头、Service Worker 与 CDN 规则这一节是全文的核心直接给可复制的配置片段。先配 HTTP 缓存头这是最底层也最容易被忽视的一层。不同资源类型必须差异化不能一个max-age走天下。对于构建时注入内容哈希的静态资源比如/_next/static/下的文件配置成永久缓存{ source: /_next/static/:path*, headers: [ { key: Cache-Control, value: public, max-age31536000, immutable } ] }immutable的作用是告诉浏览器这个 URL 对应的内容永不改变即使用户按 CtrlF5 强制刷新也不要发条件请求。因为文件名里已经带了内容哈希内容一变文件名就变旧 URL 自然失效。对于 HTML 文档页用stale-while-revalidate平衡新鲜度和速度{ source: /:path*, headers: [ { key: Cache-Control, value: public, max-age60, stale-while-revalidate86400 } ] }含义是60 秒内直接返回缓存60 秒后缓存进入“陈旧但可用”边缘节点仍立即返回旧副本同时后台回源验证验证到新版本后下次请求拿到新的。用户全程亚秒级内容误差控制在 60 秒内。接下来是 Service Worker。用 Workbox 可以少写很多样板代码但你要理解每条路由策略在干什么// sw.js import { precacheAndRoute } from workbox-precaching; import { registerRoute } from workbox-routing; import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from workbox-strategies; import { ExpirationPlugin } from workbox-expiration; // 构建工具注入的资源清单安装阶段批量预缓存 precacheAndRoute(self.__WB_MANIFEST); // 图片缓存优先最多 100 张30 天过期 registerRoute( ({ request }) request.destination image, new CacheFirst({ cacheName: images, plugins: [ new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 }), ], }) ); // AI 问答接口网络优先10 秒超时回退缓存 registerRoute( ({ url }) url.pathname.startsWith(/api/ai), new NetworkFirst({ cacheName: ai-cache, networkTimeoutSeconds: 10, plugins: [ new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 5 * 60 }), ], }) ); // 文档内容即时响应 后台刷新 registerRoute( ({ url }) url.pathname.startsWith(/docs/), new StaleWhileRevalidate({ cacheName: docs-content, plugins: [ new ExpirationPlugin({ maxEntries: 200, maxAgeSeconds: 24 * 60 * 60 }), ], }) );离线兜底也要配否则用户断网访问未缓存路由会看到浏览器默认错误页import { setCatchHandler } from workbox-routing; setCatchHandler(async ({ request }) { if (request.destination document) { return caches.match(/offline.html); } return Response.error(); });最后是 CDN 缓存规则。不同 CDN 厂商控制台字段名不一样但核心逻辑一致按路径或文件后缀匹配设置边缘缓存 TTL 和回源策略。以常见的规则配置为例# CDN 缓存规则示例伪配置按厂商控制台字段对应填写 [[rules]] match /_next/static/* edge_ttl 31536000 ignore_query true origin origin-pool [[rules]] match /docs/* edge_ttl 60 stale_while_revalidate 86400 origin origin-pool [[rules]] match /api/* edge_ttl 0 bypass true注意/api/*设成不缓存因为 API 响应通常带用户态缓存会串数据。AI 接口的缓存交给 Service Worker 的 NetworkFirst 处理而不是 CDN。如果你用 Next.jsnext.config.ts里可以统一收口import type { NextConfig } from next; const config: NextConfig { experimental: { staleTimes: { dynamic: 30, static: 300 }, }, headers: async () [ { source: /_next/static/:path*, headers: [ { key: Cache-Control, value: public, max-age31536000, immutable }, ], }, { source: /:path*, headers: [ { key: Cache-Control, value: public, max-age60, stale-while-revalidate86400 }, ], }, ], images: { minimumCacheTTL: 60 * 60 * 24 * 30, formats: [image/avif, image/webp], }, }; export default config;这套配置落地后静态资源、文档页、图片、AI 接口各有各的策略不会互相打架。4. 验证请求用 DevTools 看三层命中情况配完不验证等于没配。打开 Chrome DevTools切到 Network 面板勾选 Disable cache 先关掉否则浏览器缓存层被绕过你测不到真实效果。刷新页面看 Size 列显示(disk cache)或(memory cache)说明 HTTP 缓存命中显示具体字节数说明走了网络。判断 CDN 是否命中看响应头里的x-cache、cf-cache-status、x-vercel-cache之类字段不同厂商名字不同。HIT表示边缘命中MISS表示回源了。如果一直是 MISS检查 CDN 规则里的路径匹配是否写对以及源站返回的Cache-Control是否被 CDN 尊重。判断 Service Worker 是否接管在 Application 面板看 Service Workers 状态是否为 activated and running再看 Cache Storage 里有没有images、docs-content、ai-cache这些桶。点进某个桶能看到具体缓存了哪些请求。如果桶是空的说明预缓存清单没注入成功检查构建产物里有没有__WB_MANIFEST的替换结果。验证 AI 接口缓存可以在 Console 里手动发两次同样的请求// 第一次走网络 fetch(/api/ai, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ prompt: 解释一下 stale-while-revalidate }), }).then(r r.json()).then(console.log); // 断网后再发一次应命中 ai-cache把 Network 面板的 throttling 调成 Offline再发一次如果还能拿到结果说明 NetworkFirst 的超时回退生效了。注意 POST 请求默认不被 Cache API 缓存如果你要缓存 POST需要在 Service Worker 里手动构造 Request 并指定 method或者改成 GET 加查询参数。验证 HTTP 缓存的immutable是否生效可以在 Network 面板选中一个main.xxxx.js看 Response Headers 里有没有cache-control: public, max-age31536000, immutable。然后按 CtrlF5 强制刷新如果 Size 列仍显示(disk cache)说明 immutable 起作用了浏览器跳过了条件请求。一个容易忽略的点Service Worker 本身也会被 HTTP 缓存。如果sw.js被缓存太久用户可能一直跑旧版本。所以sw.js必须设成Cache-Control: no-cache让浏览器每次都验证。这个在 Workbox 生成的配置里通常会自动处理但你要确认一下。5. 常见报错排查401、local proxy failed 与 reading choices配缓存和接 AI 接口时有几类报错几乎一定会遇到。我按真实错误信息逐个拆。401 Unauthorized。这个通常出现在调用 TaoToken API 时。原因无非三种Key 没填、Key 填错、Key 被前端暴露后失效。检查你的后端环境变量里TAOTOKEN_API_KEY是否正确请求头是不是Authorization: Bearer key。如果你把 Key 写进了前端浏览器 Network 里能看到明文赶紧换 Key 并改成后端转发。三件套再确认一遍Base URL 是https://taotoken.net/apiKey 来自控制台Model ID 拼写无误。local proxy failed。这个报错一般出现在本地开发环境你配了某个代理但代理没起来或者端口冲突。检查你的开发服务器代理配置确认目标地址可达。如果你在 Service Worker 里拦截了/api/请求但本地代理没启动NetworkFirst 会先等 10 秒超时再回退缓存开发时体感很慢。开发环境可以把networkTimeoutSeconds调小到 3 秒或者干脆在localhost下不注册 Service Worker。reading choices。这是解析模型响应时的经典错误意思是代码在访问response.choices[0]但response结构不对。常见原因是请求根本没成功返回的是错误对象或者你用的模型返回格式和 OpenAI 格式不一致。先console.log完整响应确认有没有choices字段。如果走 TaoToken它默认兼容 OpenAI 格式正常应该有。如果没有检查 Model ID 是否填错或者请求体里stream参数和你的解析逻辑是否匹配。OAuth 相关报错。如果你用 Claude Code 或类似工具接入可能会遇到 OAuth 认证失败。这类工具通常需要你配置 Base URL 和 Key而不是走浏览器 OAuth 流程。检查配置文件里的ANTHROPIC_BASE_URL或对应字段是否指向https://taotoken.net/apiKey 是否有效。Claude Code 的接入文档在 https://taotoken.net/doc 里面有具体的环境变量名。缓存不更新。用户反馈“改了代码还是旧页面”。排查顺序先看 CDN 是否命中旧缓存手动在控制台刷新该 URL再看 Service Worker 是否卡在 waiting 状态需要skipWaiting()和clients.claim()最后看 HTTP 缓存哈希文件名是否真的变了。如果文件名没变说明构建没注入新哈希检查构建配置。Cache Storage 里堆满旧版本。Workbox 的ExpirationPlugin会按maxEntries和maxAgeSeconds清理但如果你手动写了caches.open又没清理逻辑旧缓存会一直堆积。建议统一用 Workbox 管理或者在新 Service Worker 激活时删掉不在白名单里的旧 cacheName。6. 把缓存和模型调用一起收口长期项目的落地建议三层缓存配好之后真正决定长期效果的是“失效策略”和“调用收口”。缓存最大的敌人不是未命中而是陈旧命中——用户看到过期内容却毫不知情。文件名哈希解决静态资源失效stale-while-revalidate解决文档页失效Service Worker 的skipWaiting解决应用壳失效CDN 缓存标签解决批量失效。这四套机制要一起用不能只靠一个。模型调用这边建议把 TaoToken 的 Key 统一放在后端环境变量前端只请求自己的/api/ai。这样 AI 接口的缓存策略可以独立控制高频问题用 NetworkFirst 加 5 分钟缓存低频问题直接 bypass。如果你项目里还要跑长期编码任务或者 AgentCoding Plan 页面有更完整的方案 https://taotoken.net/coding-plan 。模型对话页面可以用来快速验证某个模型是否可用 https://taotoken.net/models 。API Keys 管理在 https://taotoken.net/console/api-keys 接入文档在 https://taotoken.net/doc 。最后给一个实操建议每次改缓存配置都在 DevTools 里把三层命中情况截图存档。CDN 看x-cacheService Worker 看 Cache StorageHTTP 缓存看 Size 列。三次访问分别验证首访看 CDN 回源和预缓存二访看 Service Worker 接管三访看 HTTP 缓存命中。这套验证流程跑顺了缓存就不再是玄学而是可度量、可回滚的工程配置。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →