前端缓存策略实战:强缓存、协商缓存与 Service Worker

前端缓存策略是前端性能优化的第一道闸门。当浏览器、CDN 与 Service Worker 协同把资源缓存在离用户最近的地方,二次访问的加载时间能从几秒压到毫秒级。理解强缓存、协商缓存与运行时缓存的区别,才能既提速又不踩「更新不生效」的坑。本文从 HTTP 缓存头讲起,手把手带你配出一套可预测、可回滚的缓存策略。

一、前端缓存的层级:资源到底缓存在哪

用户访问一个页面时,资源可能落在好几层缓存里,由近到远依次是:Service Worker 缓存(运行时缓存)内存/磁盘缓存(HTTP 缓存)CDN/反向代理缓存 → 源站。越靠近用户,命中越快、越省带宽。前面提到的 前端性能优化 Lighthouse 之所以能拿到高分,很大程度上就是靠合理的缓存分层把重复请求挡在了链路最前端。

缓存层控制方典型生命周期适用资源
Service Worker前端代码自定义(可永久)HTML/离线壳/核心 JS
HTTP 强缓存服务器响应头max-age 决定带哈希的 JS/CSS/图片
HTTP 协商缓存服务器响应头每次回源校验可能变动的接口/HTML
CDN 缓存边缘节点按 Cache-Control静态资源/ public 响应

二、强缓存:Cache-Control 与 Expires

强缓存的核心是「在有效期内,浏览器根本不发请求」。HTTP/1.0 用 Expires(绝对时间),但受服务器与客户端时钟不一致影响;HTTP/1.1 用 Cache-Control: max-age=31536000(相对秒数),更可靠。命中强缓存时,浏览器直接从本地读取,状态码显示 200 (disk cache)200 (memory cache)。最稳妥的写法是在 Nginx 里按资源类型分别设置:

# 带内容哈希的静态资源:一年强缓存,永不协商
location ~* \.(js|css|png|jpg|jpeg|gif|webp|woff2)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

# HTML:不缓存,保证每次都拿到最新入口
location = /index.html {
    add_header Cache-Control "no-cache";
}

关键点:immutable 告诉浏览器「内容永不变,连刷新都别重新校验」;HTML 一定要用 no-cache 而非 max-age,否则你发了新 JS 但 index.html 还指着旧文件名,用户就卡在旧版本。这和 Nginx 反向代理与缓存优化 里强调的「入口与静态分离」是同一思路。

三、协商缓存:Last-Modified 与 ETag 的 304

当强缓存过期,浏览器会带条件请求回源校验,这就是协商缓存。服务端返回 304 Not Modified 则不传 body,省流量;返回 200 则给新内容。两种校验方式:

# 方式一:基于修改时间
# 请求:If-Modified-Since: Wed, 27 Aug 2026 10:00:00 GMT
# 响应:Last-Modified: Wed, 27 Aug 2026 10:00:00 GMT

# 方式二:基于内容指纹(更精确,推荐)
# 请求:If-None-Match: "33a64b-9d1e"
# 响应:ETag: "33a64b-9d1e"

# 本地用 curl 验证是否命中协商缓存:
curl -I -H 'If-None-Match: "33a64b-9d1e"' https://fsdata.site/app.js
# 返回 304 即表示内容未变,浏览器可继续用本地副本

ETag 基于内容哈希,比 Last-Modified 更精准(秒级修改、内容相同但时间变了都能正确识别)。nginx 默认开启 ETag,多数框架也支持。对「可能变动的接口或 HTML」用协商缓存,既保证新鲜度又省带宽。

四、强缓存 vs 协商缓存:一张表看懂

维度强缓存协商缓存
是否发请求不发发条件请求
状态码200 (from cache)304
控制头Cache-Control / ExpiresETag / Last-Modified
刷新行为immutable 连刷新都不校验每次都回源校验
适用资源哈希命名静态资源HTML、易变动接口

五、Service Worker:把缓存控制权交回前端

HTTP 缓存归服务器管,而 Service Worker(SW)让前端自己决定缓存什么、怎么更新。它像一个跑在浏览器背后的代理,拦截 fetch 请求,可以自由返回缓存或网络响应,是实现离线可用的关键。基础骨架如下:

// sw.js —— 注册后由浏览器在后台运行
const CACHE = "v1-shell";
self.addEventListener("install", (e) => {
  e.waitUntil(caches.open(CACHE).then((c) => c.addAll(["/", "/app.js", "/style.css"])));
  self.skipWaiting();
});

self.addEventListener("fetch", (e) => {
  // 缓存优先,失败再走网络(适合离线壳)
  e.respondWith(
    caches.match(e.request).then((hit) => hit || fetch(e.request))
  );
});

页面里注册一次即可:navigator.serviceWorker.register("/sw.js")。注意 SW 只在 HTTPS(或 localhost)下生效,且第一次注册不会拦截当前页面,要等下次加载才接管——这是新手最常见的「没生效」错觉。

六、用 Workbox 落地:别手写缓存逻辑

手写 SW 容易出 bug,生产环境建议用 Google 的 Workbox。它提供预缓存(precache,构建时把清单写进 SW)和运行时缓存(runtimeCaching,按路由策略)。在 Vite / Webpack 里挂一个插件就能生成:

// vite.config.ts(workbox 插件)
import { VitePWA } from "vite-plugin-pwa";
export default {
  plugins: [VitePWA({
    registerType: "autoUpdate",
    workbox: {
      globPatterns: ["**/*.{js,css,html,png,svg}"],
      runtimeCaching: [{
        urlPattern: ({ url }) => url.pathname.startsWith("/api/"),
        handler: "NetworkFirst",   // 接口:网络优先,失败用缓存兜底
        options: { cacheName: "api-cache", networkTimeoutSeconds: 3 }
      }]
    }
  })]
};

策略选择有讲究:静态资源用 CacheFirst(命中最快),接口用 NetworkFirst(保证新鲜、断网兜底),图片用 StaleWhileRevalidate(先给旧图、后台更新)。这正是 前端构建工具演进 之后,工程化把缓存也纳入构建产物的体现。

七、构建产物哈希命名:强缓存的最佳拍档

强缓存敢设一年,前提是文件名随内容变。只要内容改了,哈希就变,URL 跟着变,旧缓存自动失效。所有现代打包器都支持:

// webpack:输出带 contenthash 的文件名
output: { filename: "[name].[contenthash:8].js" }

// vite 默认即是:assets/index-a1b2c3d4.js
// 这样 index.html 永远引用最新哈希名,旧文件安心躺在强缓存里

「HTML 不缓存 + 静态资源一年强缓存 + 文件名带哈希」是前端缓存的黄金三角。配合 React 性能优化 的组件级记忆化,首屏与交互都能显著提速。

八、缓存更新的三大坑与回滚

  • 坑一:HTML 被强缓存——用户永远拿不到新入口。HTML 必须 no-cache 或加版本号。
  • 坑二:SW 旧缓存挡路——改了逻辑却还是旧 SW。用 skipWaiting + clients.claim 或 autoUpdate 自动激活新版本。
  • 坑三:发版后白屏——新 HTML 引用了新 JS,但 CDN 还没回源到。发版前先推静态资源、再推 HTML(顺序不能反)。

回滚原则:静态资源因带哈希永不冲突,回滚只需把 HTML 指回旧文件名;SW 出问题则发布一个新 SW 清空缓存(caches.keys().then(ks => Promise.all(ks.map(k => caches.delete(k)))))。

九、把缓存命中率纳入监控

缓存配得好不好,要用数据说话。前端可通过 performance.getEntriesByType("resource") 统计 transferSize === 0 的比例,结合 前端性能监控 Web Vitals 把「缓存命中率」「CDN 回源率」做成看板。命中率突然下跌,往往意味着发版顺序错了或缓存头被改坏,能在用户投诉前发现。

结语

前端缓存策略不是「加一句 Cache-Control」那么简单,而是 HTTP 强缓存、协商缓存、Service Worker 与构建哈希四者协同的系统工程。记住黄金三角:HTML 不缓存、哈希静态资源长强缓存、SW 接管离线壳。当你把缓存命中率也接入监控,性能优化才真正形成闭环。把这套策略带进下一次发版,你的页面二次访问速度会让你自己都惊讶。

上一篇 远程协作异步沟通实战:工程师高效协作指南
下一篇 大模型行业月度动态:2026年8月趋势盘点