解决Jekyll博客Service Worker缓存问题
在使用Jekyll Chirpy搭建的本博客中,我遇到了一个很奇怪的缓存问题。使用Ctrl+R刷新页面后页面内容没有更新;在新设备访问页面,内容可以更新;使用Ctrl+Shift+R强制刷新后页面内容更新了,然而此后使用Ctrl+R竟然回退到了旧版。
这个问题很早就存在了,然而我一直没有深入研究,并认为和Cloudflare缓存有关。但是这无法解释这种奇怪的行为,更无法解释为什么新设备上第一次打开可以看到最新内容。
按照我之前的理解,Ctrl+Shift+R会忽略缓存进行刷新,同时更新本地的缓存。因此按理来说不可能遇到回退情况。后来经过研究,发现是Service Worker引起的问题。
Service Worker是一种运行在浏览器后台的脚本,可以拦截网络请求并进行缓存管理。Chirpy博客使用了Service Worker来实现离线浏览和加速页面加载。这与一个名为PWA的选项有关。
PWA的全称是Progressive Web App,也就是渐进式网页应用。启用PWA后,在手机浏览器上访问网站可以选择将网站作为应用安装。为了允许手机上离线阅读,博客会注册一个Service Worker来缓存页面资源。这个Service Worker太喜欢缓存了,以至于即使按下Ctrl+R也无法更新内容。
sw.js就是Service Worker的脚本文件。它定义了如何缓存资源以及如何响应网络请求。Chirpy的sw.js中缓存的核心逻辑如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
return response; // 缓存命中 → 直接返回缓存版本(即使可能已过时)
}
return fetch(event.request).then((response) => {
// ... 条件检查 ...
if (purge || event.request.method !== 'GET' || !verifyUrl(url)) {
return response; // 不缓存,直接返回网络响应
}
// 网络获取成功 → 克隆并放入缓存,供下次使用
let responseToCache = response.clone();
caches.open(swconf.cacheName).then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
而purge之类的选项由swconf.js提供:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
---
layout: compress
permalink: '/:path/swconf.js'
# Note that this file will be fetched by the ServiceWorker, so it will not be cached.
---
const swconf = {
{% if site.pwa.cache.enabled %}
cacheName: 'chirpy-{{ "now" | date: "%s" }}',
{%- comment -%} Resources added to the cache during PWA installation. {%- endcomment -%}
resources: [
'{{ "/assets/css/:THEME.css" | replace: ':THEME', site.theme | relative_url }}',
'{{ "/" | relative_url }}',
{% for tab in site.tabs %}
'{{- tab.url | relative_url -}}',
{% endfor %}
{% assign cache_list = site.static_files | where: 'swcache', true %}
{% for file in cache_list %}
'{{ file.path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
{% endfor %}
],
interceptor: {
{%- comment -%} URLs containing the following paths will not be cached. {%- endcomment -%}
paths: [
{% for path in site.pwa.cache.deny_paths %}
{% unless path == empty %}
'{{ path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
{% endunless %}
{% endfor %}
],
{%- comment -%} URLs containing the following prefixes will not be cached. {%- endcomment -%}
urlPrefixes: [
{% if site.analytics.goatcounter.id != nil and site.pageviews.provider == 'goatcounter' %}
'https://{{ site.analytics.goatcounter.id }}.goatcounter.com/counter/'
{% endif %}
]
},
purge: false
{% else %}
purge: true
{% endif %}
};
关键在于swconf.purge。如果启用了PWA缓存(site.pwa.cache.enabled为true),purge就会被设置为false。这样一来,Service Worker在拦截请求时总是优先返回缓存版本,导致Ctrl+R无法更新内容。
于是为了解决问题,我们可以在_config.yml中禁用PWA缓存:
1
2
3
pwa:
cache:
enabled: false
这样移动端的用户仍然可以安装PWA,但Service Worker不会缓存页面资源,从而避免了缓存过度的问题。