# Service worker 缓存策略

> 五种标准缓存策略——Cache First、Network First、Stale-While-Revalidate、Network Only、Cache Only——各自的适用场景及 Workbox 实现方式。

**一句话：** 缓存策略是 service worker 在 `fetch` 事件触发时遵循的规则——先查缓存、先走网络，还是两者结合，以及以何种顺序。五种标准策略覆盖了 PWA 所需的所有离线与性能权衡场景。

## 五种策略

### Cache First（缓存优先，缺失时回退到网络）

先查缓存，命中则直接返回；未命中时从网络获取，并可选地将结果存入缓存。

**适用于：** 内容长期稳定的静态资源（应用外壳、字体、图片、带版本号的 JS 包）。缓存命中即刻响应；未命中则承受完整的网络往返延迟。

**不适用于：** 必须始终保持最新的资源（用户数据、价格、鉴权 Token）。

```js
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      if (cached) return cached;
      return fetch(event.request).then((response) => {
        const clone = response.clone();
        caches.open('v1').then((cache) => cache.put(event.request, clone));
        return response;
      });
    })
  );
});
```

### Network First（网络优先，失败时回退到缓存）

优先尝试网络；成功则缓存并返回最新响应；网络失败时回退到缓存中已有的内容。

**适用于：** 希望内容及时但可以接受旧版离线兜底的页面（新闻流、仪表盘）。

**不适用于：** 大体积、变更不频繁的静态资源——每次请求都要支付网络开销。

```js
self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then((response) => {
        const clone = response.clone();
        caches.open('v1').then((cache) => cache.put(event.request, clone));
        return response;
      })
      .catch(() => caches.match(event.request))
  );
});
```

### Stale-While-Revalidate（旧版本立即响应，后台更新）

立即返回缓存版本（速度快），同时在后台获取最新内容并更新缓存，供下次使用。

**适用于：** 速度优先于绝对新鲜度的资源（头像图片、非关键 API 数据、次要脚本）。用户始终能快速看到内容；后台请求保持缓存热度。

**不适用于：** 在同一次页面加载中，与更新版本并存会导致错误的资源。

```js
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.open('v1').then((cache) =>
      cache.match(event.request).then((cached) => {
        const networkFetch = fetch(event.request).then((response) => {
          cache.put(event.request, response.clone());
          return response;
        });
        return cached ?? networkFetch;
      })
    )
  );
});
```

### Network Only（仅走网络）

始终从网络获取，service worker 不做任何缓存。

**适用于：** 绝不能使用旧版本的请求：埋点上报、POST 写操作、支付请求。本质上是透传。

### Cache Only（仅用缓存）

只从缓存返回内容；若缓存中没有，请求直接失败。

**适用于：** 在 `install` 阶段预缓存、确保一定存在的资源（离线外壳）。切勿用于动态或用户专属内容。

## 策略选择参考

| 资源类型 | 推荐策略 |
|---|---|
| 应用外壳（HTML、CSS、核心 JS） | Cache First，配合带版本号的缓存名称 |
| 带版本号的静态资源（`/assets/main.abc123.js`） | Cache First——URL 本身在部署时会变化 |
| API 响应、需要及时更新的页面 | Network First，搭配离线兜底 |
| 头像、缩略图、次要字体 | Stale-While-Revalidate |
| 鉴权、支付、埋点 | Network Only |
| 离线兜底页面 | Cache Only（在 `install` 阶段预缓存） |

## 使用 Workbox

Workbox 将五种策略封装为可组合的类，无需手写 `fetch` 事件处理器：

```js
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

// 应用外壳——网络优先，带页面缓存
registerRoute(
  ({ request }) => request.mode === 'navigate',
  new NetworkFirst({ cacheName: 'pages' })
);

// 图片——旧版立即响应
registerRoute(
  ({ request }) => request.destination === 'image',
  new StaleWhileRevalidate({ cacheName: 'images' })
);

// 带版本号的静态资源——缓存优先，长期有效
registerRoute(
  ({ url }) => url.pathname.startsWith('/assets/'),
  new CacheFirst({
    cacheName: 'static-assets',
    plugins: [new CacheableResponsePlugin({ statuses: [200] })],
  })
);
```

完整配置详情请参阅 [Workbox](/zh/reference/service-worker/workbox/)。

## 实践清单

- [ ] 按资源类型选择策略，而非对所有资源一刀切。
- [ ] 为缓存名称加版本号（如 `shell-v3`），并在 `activate` 中删除旧缓存。
- [ ] 切勿缓存非幂等请求（POST、PUT、DELETE），除非是在故意构建后台同步队列。
- [ ] Cache First 资源须将 URL 与内容哈希绑定，确保部署后不会从缓存中提供过期内容。
- [ ] 添加明确的离线兜底页面，使 Network First 在网络失败时返回可用响应而非浏览器报错。详见[离线兜底](/zh/reference/service-worker/offline-fallback/)。

## 相关参考

- [Cache API](/zh/reference/service-worker/cache-api/) — `caches.open()`、`cache.put()` 与 `cache.match()` 基础 API
- [Fetch 事件与路由](/zh/reference/service-worker/fetch-event/) — `fetch` 事件如何到达 service worker
- [Workbox](/zh/reference/service-worker/workbox/) — 生产可用的策略实现
- [离线兜底](/zh/reference/service-worker/offline-fallback/) — 提供有意义的离线页面