# HTML离线跳转空白?Service Worker路由踩坑全解析 讲个现象:HTML页面离线后,点击链接跳转,结果整个页面一片空白。很多人第一反应是Service Worker没注册成功,但实际上更常见的原因是路由没兜住——在`fetch`事件里必须专门识别`document`类型的请求并返回缓
HTML Service Worker路由策略方法与避坑指南
作者:技术拾光
时间:2026-08-01
浏览:193
# HTML离线跳转空白?Service Worker路由踩坑全解析
讲个现象:HTML页面离线后,点击链接跳转,结果整个页面一片空白。很多人第一反应是Service Worker没注册成功,但实际上更常见的原因是路由没兜住——在`fetch`事件里必须专门识别`document`类型的请求并返回缓存的HTML,否则浏览器根本不会渲染任何内容。
来看一张典型的错误表现截图:

## 如何正确拦截HTML跳转请求
用户点击``或者直接在地址栏输入路径时,触发的是`destination === 'document'`类型的请求。这类请求与`script`、`style`完全不同,必须单独处理,否则缓存逻辑不会生效。
先看两种常见的写法对比:
- **错误写法**:`event.respondWith(caches.match(event.request))`——这会匹配到POST请求、带cookie的请求,结果要么登录态丢失,要么表单静默失败。
- **正确写法**:只对`event.request.destination === 'document'`做缓存优先处理,其他类型的请求按需分流。
这里有一个关键点:SPA的history模式(比如React Router、Vue Router)下,所有路由其实都指向同一份`index.html`。所以缓存时必须包含这个HTML文件本身,而不是缓存`/about`这种实际不存在的路径。
## 为什么缓存了"/"但"/about"离线仍打不开
很多人会这样写:`cache.addAll(['/', '/app.js'])`。问题在于,这只缓存了根路径的HTML。浏览器访问`/about`时发送的是全新请求,`caches.match()`查不到对应的缓存键——因为缓存键是完整的URL,不是路由规则。
**解决办法一(推荐)**:服务端配置fallback。比如Nginx的`try_files $uri $uri/ /index.html`,让所有未命中静态资源的请求都返回`index.html`。Service Worker则只缓存这个`/index.html`。
**解决办法二(客户端兜底)**:在`fetch`事件里,对`destination === 'document'`的请求直接通过`caches.match('/index.html')`返回,不依赖路径匹配。
还有一个容易忽略的点:**切勿缓存带查询参数的HTML路径**,比如`/postid=123`。这类动态路径无法预加载,也不应该放进Cache API。
## Wouter内存路由如何配合SW离线
Wouter默认使用`useBrowserLocation`,依赖History API。离线时跳转就会失败。如果换成`memoryLocation`,路由状态保存在内存里,不发送网络请求,天然适配离线导航——前提是HTML和JS已经缓存了,否则页面都加载不了。
具体操作时需注意几个细节:
- `sw.js`的`install`阶段必须缓存`/index.html`和Wouter相关的JS文件(比如`/node_modules/wouter/esm/index.js`)。
- `memoryLocation`不会改变URL,所以用户刷新页面时仍然会走`destination === 'document'`流程,Service Worker必须能返回缓存的HTML。
- 不要在`memoryLocation`初始化时传入服务端生成的动态路径(比如`path: window.location.pathname`),这会导致首次加载就依赖网络。
## 离线跳转后JS报错或白屏的真正原因
控制台经常报`React hydration error`或`Cannot find module`,表面看是框架的问题,实质上是HTML和JS缓存不同步:Service Worker返回了旧版`index.html`,但里面引用的`app.a1b2c3.js`已经更新,而这个JS文件没有被重新缓存。
解决思路十分清晰:
- 每次发布新版本,必须更新`urlsToCache`数组里的JS/CSS路径(带上哈希值),或者改用Workbox的`precacheAndRoute()`自动生成清单。
- 不要在`install`事件里用`fetch('/index.html')` + `cache.put()`动态缓存——首次访问时Service Worker还没激活,请求已经发出,自然缓存为空。
- 前往DevTools → Application → Cache Storage检查,确认HTML和它引用的所有资源都在同一个cache里,并且时间戳一致。
**最容易被忽略的点**:`skipWaiting()`和`clients.claim()`如果不加,新版Service Worker永远不会接管已经打开的页面。用户刷新十次,用的还是旧逻辑。这不是缓存问题,而是生命周期卡住了。
实用工具推荐
电子包浆
使用 ›绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
图片压缩
使用 ›选择画质和输出格式,预览压缩前后的大小。