为什么我的PWA应用在离线模式下无法加载本地图片资源?

艺馨 阅读 20

我在开发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'
      ]);
    })
  );
});
我来解答 赞 7 收藏
二维码
手机扫码查看
2 条解答
彦杰 Dev
你这个问题出在缓存列表没把图片加进去。Service Worker 只会缓存你在 cache.addAll 里声明的资源,你列了一堆 HTML、CSS、JS,但 ./assets/logo.png 根本不在里面,离线当然拿不到。

504 Gateway Timeout 看着像是网络中断导致请求卡住,本质是请求发出去了但没有被拦截,也没缓存,浏览器试图走网络加载又失败。

标准写法是在 install 阶段就把所有关键静态资源预缓存,包括图片:

self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
'/assets/logo.png' // 漏了这句
]);
})
);
});


另外建议在 fetch 事件里做兜底:

self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});


这样就算 install 阶段漏掉个别资源,后续请求还能从缓存命中。不过最保险还是预先把所有离线需要的资源列全。manifest 里的 icon 是用于安装图标,不会自动进缓存,别搞混了。
点赞 4
2026-02-11 18:12
轩辕建宇
这个问题的关键是你的 service worker 缓存列表里根本没有包含那些图片资源,但你却指望离线时它们能加载出来。浏览器在离线状态下只能从缓存中读取内容,而你只缓存了 HTML、CSS 和 JS,./assets/logo.png 根本不在 cache.addAll() 的数组里,当然拿不到。

更严重的是那个 504 错误,其实不是网关问题,而是 service worker 没有正确处理请求,导致浏览器试图通过网络获取资源,但离线状态下这个请求直接超时,所以显示 504 —— 实际上是“无法连接”的一种表现形式。

要解决这个问题,得从两个层面入手:预缓存资源 和 运行时缓存回退机制。

第一步,你必须把所有静态资源路径明确列进 install 阶段的缓存列表。比如你的 logo.png 如果在 /assets/logo.png,那就要写进去:

self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
'/assets/logo.png' // 看到了吗?少了这一行,图片就没法离线访问
]);
})
);
});


注意路径必须和实际请求路径完全一致。如果你用的是相对路径 ./assets/logo.png,最终解析出来可能还是 /assets/logo.png,所以这里一定要以根路径为准。

但这还不够。万一你后面加了新图片,或者路径拼写有个小错误,难道每次都要手动更新缓存列表?所以我们需要第二个机制:fetch 事件拦截。

加上 fetch 监听器,让 service worker 能在运行时尝试从缓存命中请求:

self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then(response => {
// 如果缓存中有匹配项,直接返回
if (response) {
return response;
}
// 否则尝试走网络(在线时)
return fetch(event.request);
})
);
});


这样即使某个资源没被预缓存,在线状态下第一次访问也会被后续逻辑捕获。不过为了真正可靠的离线体验,预缓存才是王道。

还有一点容易被忽略:manifest.json 里的 start_url 只影响 PWA 启动入口,并不会自动帮你缓存任何资源。很多人以为注册了 manifest 就万事大吉,其实 service worker 才是控制缓存的核心。

另外建议你给缓存版本命名更清晰一点,比如不要一直叫 'v1',升级时换成 'v2',否则旧缓存不更新,新资源永远进不去。可以考虑在 install 阶段清掉旧缓存:

self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys => {
return Promise.all(
keys.filter(key => key !== 'v1') // 假设当前要用 v1
.map(key => caches.delete(key))
);
})
);
});


总结一下:你现在的问题就是典型的“以为路径对就能访问”,但实际上离线模式下所有资源都必须提前放进 cache。浏览器不会因为文件在本地目录就自动让它可用,一切都要靠 service worker 显式管理。

先把图片路径加到 cache.addAll 里,再确保 fetch 事件被捕获,这两个步骤缺一不可。改完之后 hard refresh 一次,然后断开网络试试,应该就能正常看到图片了。
点赞 2
2026-02-11 10:04