为什么我的PWA应用在离线模式下无法加载本地图片资源?
我在开发PWA时配置了service worker和manifest,但离线状态下页面里的图片都显示不出来。明明在network标签里看到图片路径是正确的,./assets/logo.png这种本地路径。
我尝试过在service worker里用caches.match(event.request)处理请求,也注册了manifest里的start_url。但离线刷新时控制台报错:GET http://localhost:3000/assets/logo.png [HTTP/1.1 504 Gateway Timeout],这是怎么回事?
这是我的service worker缓存配置:
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js'
]);
})
);
});
504 Gateway Timeout 看着像是网络中断导致请求卡住,本质是请求发出去了但没有被拦截,也没缓存,浏览器试图走网络加载又失败。
标准写法是在 install 阶段就把所有关键静态资源预缓存,包括图片:
另外建议在 fetch 事件里做兜底:
这样就算 install 阶段漏掉个别资源,后续请求还能从缓存命中。不过最保险还是预先把所有离线需要的资源列全。manifest 里的 icon 是用于安装图标,不会自动进缓存,别搞混了。
./assets/logo.png根本不在cache.addAll()的数组里,当然拿不到。更严重的是那个 504 错误,其实不是网关问题,而是 service worker 没有正确处理请求,导致浏览器试图通过网络获取资源,但离线状态下这个请求直接超时,所以显示 504 —— 实际上是“无法连接”的一种表现形式。
要解决这个问题,得从两个层面入手:预缓存资源 和 运行时缓存回退机制。
第一步,你必须把所有静态资源路径明确列进 install 阶段的缓存列表。比如你的 logo.png 如果在
/assets/logo.png,那就要写进去:注意路径必须和实际请求路径完全一致。如果你用的是相对路径
./assets/logo.png,最终解析出来可能还是/assets/logo.png,所以这里一定要以根路径为准。但这还不够。万一你后面加了新图片,或者路径拼写有个小错误,难道每次都要手动更新缓存列表?所以我们需要第二个机制:fetch 事件拦截。
加上 fetch 监听器,让 service worker 能在运行时尝试从缓存命中请求:
这样即使某个资源没被预缓存,在线状态下第一次访问也会被后续逻辑捕获。不过为了真正可靠的离线体验,预缓存才是王道。
还有一点容易被忽略:manifest.json 里的 start_url 只影响 PWA 启动入口,并不会自动帮你缓存任何资源。很多人以为注册了 manifest 就万事大吉,其实 service worker 才是控制缓存的核心。
另外建议你给缓存版本命名更清晰一点,比如不要一直叫 'v1',升级时换成 'v2',否则旧缓存不更新,新资源永远进不去。可以考虑在 install 阶段清掉旧缓存:
总结一下:你现在的问题就是典型的“以为路径对就能访问”,但实际上离线模式下所有资源都必须提前放进 cache。浏览器不会因为文件在本地目录就自动让它可用,一切都要靠 service worker 显式管理。
先把图片路径加到 cache.addAll 里,再确保 fetch 事件被捕获,这两个步骤缺一不可。改完之后 hard refresh 一次,然后断开网络试试,应该就能正常看到图片了。