Service Worker缓存HTML页面失败 访问报ERR_FAILED错误
问题现象
未通过Service Worker将页面加入缓存时,页面跳转访问无异常;将目标页面加入缓存列表后,无论在线、离线环境均无法访问对应页面,该问题仅出现在页面类HTML资源上,.jpg、.png、.js、.css及公共HTML片段(如导航栏、页脚)等资源的缓存、访问均正常。
故障复现场景
将页面加入缓存后跳转至对应页面时,浏览器返回错误:无法访问站点,位于页面链接的网页可能暂时无法访问,或是已永久移动到新的web地址,错误码:ERR_FAILED。
当前Service Worker实现代码
const staticChacheName = 'site-static' const assets = [ '/', '/index.html', '/Html/Elements/footer.html', '/Html/Elements/mobile_menu.html', '/Html/Elements/navbar.html', '/Html/Pages/film.html', //not working '/Html/Pages/anime.html', //not working '/Html/Pages/sitobello.html', //not working '/Html/Pages/song.html', //not working '/Html/Pages/amici_home.html', //not working '/Html/Pages/Amici/tilde.html', //not working '/Html/Pages/Amici/kloe.html', //not working 'Css/Index/style.css', 'Css/Sito Bello/background.css', 'Css/Sito Bello/section.css', 'Css/Sito Bello/settings.css', 'Css/Song/background.css', 'Css/Song/elementi.css', 'Css/Film/background.css', 'Css/Film/elementi.css', 'Css/Anime/background.css', 'Css/Anime/elementi.css', 'Css/Element/footer.css', 'Css/Element/mobile_menu.css', 'Css/Element/navbar.css', 'Css/Element/scrollbar.css', 'Css/Amici/Home/background.css', 'Css/Amici/Home/elementi.css', 'Css/Amici/Kloe/background.css', 'Css/Amici/Tilde/background.css', '/Images/Logo.png', '/Images/Index/background.jpg', '/Javascript/Element/navbar.js', '/Javascript/Film/add.js', '/Javascript/Sito Bello/add.js', '/Javascript/Sito Bello/settings.js', '/manifest.webmanifest', '/Javascript/app.js', ] //install service worker self.addEventListener('install', evt => { evt.waitUntil( caches.open(staticChacheName).then(chache => { console.log('chaching shell assets') chache.addAll(assets) })) }) //activate service worker self.addEventListener('activate', evt => { }) self.addEventListener('fetch', evt => { evt.respondWith( caches.match(evt.request).then(chacheRes => { return chacheRes || fetch(evt.request) }) ) })
故障页面示例(以film.html为例)
<!DOCTYPE html> <html lang="it"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"> <link rel="stylesheet" href="/Css/Element/navbar.css"> <link rel="stylesheet" href="/Css/Element/mobile_menu.css"> <link rel="stylesheet" href="/Css/Film/background.css"> <link rel="stylesheet" href="/Css/Film/elementi.css"> <link rel="stylesheet" href="/Css/Element/footer.css"> <link rel="stylesheet" href="/Css/Element/scrollbar.css"> <link rel="manifest" href="/manifest.webmanifest"> <link rel = "icon" href = "/Images/Logo.png" type = "image/x-icon"> <title>Aside - Film</title> </head> <body> <nav></nav> <div id="mb-menu"></div> <h1 id="title">Film</h1> <div id="films"> <!-- aggiunge film --> </div> <footer></footer> <script src="/Javascript/app.js"></script> <script src="/Javascript/Film/add.js"></script> <script src="/Javascript/Element/navbar.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <script> $(function(){ $("nav").load("/Html/Elements/navbar.html"); $("footer").load("/Html/Elements/footer.html"); $("#mb-menu").load("/Html/Elements/mobile_menu.html"); }); </script> </body> </html>
站点结构参考

故障原因定位
共3处核心配置错误:
- 缓存资源列表路径错误,导致缓存写入中断
- 所有Css开头的缓存路径都漏了根路径前缀
/,和页面实际引用的/Css/xxx路径不匹配,请求直接返回404 - 路径大小写不统一:
/Html/Pages/amici_home.html使用小写amici目录,同目录下的tilde、kloe页面路径写为大写Amici目录,大小写敏感的服务器上这两个路径会返回404 - 带空格的路径未做URL编码:
/Javascript/Sito Bello/目录名包含空格,实际请求时空格会被编码为%20,和缓存列表中的原始字符串不匹配,请求返回404 cache.addAll是原子操作,只要列表中任意一个资源请求失败,整个缓存写入流程就会直接中断。
- 所有Css开头的缓存路径都漏了根路径前缀
- Service Worker安装逻辑错误,提前激活拦截请求
install事件回调中执行chache.addAll(assets)时没有返回对应Promise,导致evt.waitUntil()没有等待缓存全部写入完成就判定Service Worker安装成功,提前激活开始拦截网络请求。此时排在资源列表后面的页面类HTML要么还没写入缓存,要么写入中断返回残缺响应,浏览器收到无效响应直接报ERR_FAILED。 - fetch拦截逻辑缺少兜底,activate事件未做初始化处理
- 现有fetch逻辑没有异常捕获,当缓存匹配失败、网络请求也出错时,会直接抛出异常,没有降级返回可用缓存
- 没有针对导航类HTML请求做适配,URL参数、大小写差异都会导致缓存匹配失败
- activate事件没有调用
self.clients.claim(),Service Worker激活后无法立即接管已打开的页面,新旧缓存混用会进一步加剧加载异常;同时没有旧缓存清理逻辑,修改资源后用户端不会更新缓存。
修复方案
- 统一修正缓存资源列表的路径,所有路径统一加根前缀
/,对齐实际目录大小写,带空格的路径把空格替换为%20,或者直接修改文件夹名去掉空格避免编码问题 - install事件中返回
cache.addAll的Promise,确保所有资源缓存完成后再激活Service Worker - 完善fetch拦截逻辑,增加异常捕获,对导航类请求增加兜底规则
- activate事件增加旧缓存清理逻辑,调用
clients.claim()立即接管页面
修复后的核心Service Worker代码参考:
// 升级缓存版本号,后续修改静态资源后递增版本号即可自动清理旧缓存 const staticCacheName = 'site-static-v2' const assets = [ '/', '/index.html', '/Html/Elements/footer.html', '/Html/Elements/mobile_menu.html', '/Html/Elements/navbar.html', '/Html/Pages/film.html', '/Html/Pages/anime.html', '/Html/Pages/sitobello.html', '/Html/Pages/song.html', '/Html/Pages/amici_home.html', // 统一目录大小写,与实际服务器路径对齐 '/Html/Pages/amici/tilde.html', '/Html/Pages/amici/kloe.html', // 补全所有Css路径的根前缀/ '/Css/Index/style.css', '/Css/Sito%20Bello/background.css', '/Css/Sito%20Bello/section.css', '/Css/Sito%20Bello/settings.css', '/Css/Song/background.css', '/Css/Song/elementi.css', '/Css/Film/background.css', '/Css/Film/elementi.css', '/Css/Anime/background.css', '/Css/Anime/elementi.css', '/Css/Element/footer.css', '/Css/Element/mobile_menu.css', '/Css/Element/navbar.css', '/Css/Element/scrollbar.css', '/Css/Amici/Home/background.css', '/Css/Amici/Home/elementi.css', '/Css/Amici/Kloe/background.css', '/Css/Amici/Tilde/background.css', '/Images/Logo.png', '/Images/Index/background.jpg', '/Javascript/Element/navbar.js', '/Javascript/Film/add.js', // 空格替换为%20,建议直接修改文件夹名去掉空格 '/Javascript/Sito%20Bello/add.js', '/Javascript/Sito%20Bello/settings.js', '/manifest.webmanifest', '/Javascript/app.js', ] // 安装Service Worker self.addEventListener('install', evt => { // 等待所有缓存写入完成再结束安装流程 evt.waitUntil( caches.open(staticCacheName).then(cache => { console.log('caching shell assets') // 必须return addAll的promise return cache.addAll(assets) }) ) // 安装完成后立即跳过等待,激活新版本 self.skipWaiting() }) // 激活Service Worker self.addEventListener('activate', evt => { evt.waitUntil( // 清理旧版本缓存 caches.keys().then(keys => { return Promise.all(keys .filter(key => key !== staticCacheName) .map(key => caches.delete(key)) ) }) ) // 立即接管所有已打开的客户端页面 self.clients.claim() }) // 拦截fetch请求 self.addEventListener('fetch', evt => { // 只处理同域GET请求,跨域CDN资源直接走网络 if (evt.request.method !== 'GET' || !evt.request.url.startsWith(self.location.origin)) { return } evt.respondWith( caches.match(evt.request, { // 忽略URL查询参数匹配,避免带参数的请求匹配不到缓存 ignoreSearch: true, // 忽略Vary头匹配 ignoreVary: true }).then(cacheRes => { // 缓存命中直接返回,未命中走网络请求,请求成功后写入缓存 return cacheRes || fetch(evt.request).then(fetchRes => { // 克隆响应副本写入缓存,因为响应是流只能读取一次 const resClone = fetchRes.clone() caches.open(staticCacheName).then(cache => { cache.put(evt.request, resClone) }) return fetchRes }) }).catch(() => { // 异常兜底:如果是导航HTML请求,离线时返回首页缓存 if (evt.request.mode === 'navigate') { return caches.match('/index.html') } }) ) })
修复后需要在浏览器DevTools的Application面板中卸载旧的Service Worker,清除旧缓存后重新访问站点,即可正常加载所有缓存页面,支持整站离线访问。
内容的提问来源于stack exchange,提问作者andrex 2006
相关产品推荐
相关产品推荐

