App Shell初次加载后内容不更新,如何排查?

UP主~景景 阅读 117

我在做PWA的App Shell架构时遇到奇怪问题,页面首次加载显示正常,但刷新后动态内容没更新,控制台也没报错。检查了sw.js的fetch拦截逻辑,尝试过清除缓存也不行,这是怎么回事?


self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/api/data')) {
    event.respondWith(
      caches.match(event.request)
        .then(cache => cache || fetch(event.request))
    );
  }
});

服务工作线程里用了这样的缓存策略,但数据接口的响应明明有更新,前端页面还是显示旧数据。难道是缓存策略写错了?或者需要设置Cache-Control头?

我来解答 赞 7 收藏
二维码
手机扫码查看
1 条解答
❤梓艺
❤梓艺 Lv1
你这个问题挺典型的,PWA 的 App Shell 架构下动态内容不更新,基本是缓存策略没写对,或者后端响应头没控制好缓存行为。我来一步步分析,并给出解决方案。

---

### 1. 当前代码的问题分析

你的 fetch 监听逻辑是这样的:

self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/data')) {
event.respondWith(
caches.match(event.request)
.then(cache => cache || fetch(event.request))
);
}
});


这段代码的意思是:

- 如果请求的是 /api/data 接口
- 那么先从缓存里找匹配的请求
- 如果找到了,就返回缓存数据
- 如果没找到,再去网络拉取

**这会导致一个典型问题:缓存优先(Cache-first)策略会一直返回旧数据,除非缓存过期或被清除。**

而你的问题是「刷新后动态内容没更新」,说明你期望的是每次刷新都拉取最新数据,但你用的是「先查缓存」的逻辑,这显然不符合预期。

---

### 2. 解决方案:选择正确的缓存策略

如果你希望每次刷新都获取最新数据,但又想利用缓存做离线兜底,建议使用「网络优先(Network-first)」策略。

self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/data')) {
event.respondWith(
fetch(event.request).catch(() => {
return caches.match(event.request);
})
);
}
});


这样做的逻辑是:

- 先尝试从网络请求最新数据
- 如果失败(比如离线),再从缓存里取数据兜底
- 这样刷新后就能拿到最新数据了,而不是优先用缓存

---

### 3. 如果你确实需要缓存,但想控制缓存更新时间

你可以考虑加上一个「缓存失效时间」的判断。但 Service Worker 本身没有自动过期机制,所以需要你自己写缓存策略来控制。

比如用 Cache-Control 或 Expires 头,或者自己写一个带时间戳的缓存 key。

举个例子,你可以结合 cache-control 响应头来控制缓存时间:

self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/data')) {
event.respondWith(
caches.match(event.request).then(response => {
// 如果缓存存在,并且未过期,则返回缓存数据
if (response) {
const cachedTime = parseInt(response.headers.get('X-Cached-Time'));
const now = Date.now();
const TTL = 5 * 60 * 1000; // 缓存有效期:5分钟

if (now - cachedTime < TTL) {
return response;
}
}

// 否则重新请求,并更新缓存
return fetch(event.request).then(networkResponse => {
if (networkResponse.ok) {
const clonedResponse = networkResponse.clone();
caches.open('api-cache').then(cache => {
cache.put(event.request, clonedResponse);
});
}
return networkResponse;
}).catch(() => {
return response; // 网络失败,返回旧缓存
});
})
);
}
});


然后你后端返回时加个自定义头:

X-Cached-Time: 1712345678901


这样你就能精确控制缓存的有效时间。

---

### 4. 检查响应头是否设置了 Cache-Control

有时候即使你的 Service Worker 写对了,浏览器还是用了它自己的默认缓存策略。你可以通过开发者工具的「Network」面板查看接口的响应头:

Cache-Control: no-cache, no-store, must-revalidate


如果你看到的是 max-age=31536000,那浏览器会缓存一年,Service Worker 也无能为力。

**解决办法:在后端设置正确的缓存控制头。**

---

### 5. 最后别忘了 service worker 的更新机制

如果你修改了 service worker 文件(比如 sw.js),浏览器不会自动激活新的 worker,除非你强制更新。

**你可以这样操作:**

- 打开 DevTools,Application -> Service Workers
- 点击 unregister 清除注册
- 刷新页面重新注册
- 或者更新 service worker 文件后,点击 update

---

### 总结一下你应该怎么做:

| 期望效果 | 推荐策略 |
|----------|----------|
| 每次刷新都取最新数据 | 使用 network-first 策略 |
| 更新时缓存,但要定时刷新 | 加 X-Cached-Time 头 + TTL 判断 |
| 保证缓存不会被浏览器自动缓存 | 后端设置 Cache-Control: no-cache |
| 确保 service worker 生效 | 手动 unregister + 页面刷新 |

---

如果你还卡在那儿,我建议你打开 DevTools,打开「Application」标签下的「Cache Storage」和「Service Workers」,看看缓存内容和激活状态,基本就能定位是缓存问题还是请求流程问题了。
点赞 4
2026-02-03 20:07