AIモデル Qwen/Qwen3-8B による翻訳。
原文言語:Simplified Chinese、翻訳先言語:japanese、翻訳時間:2026-05-01 05:12
。AI 翻訳は参考に限り、内容の完全な正確性を保証できません。原文をご参照ください。
半日かけてようやくサイトのService Workerのキャッシュ設定を整えました。(夏休みの初日からこんなことに時間を費やすなんて、来月には高三生になります)
以前はサイトの読み込み速度が遅く、何秒も待たないと表示されなかったのが悩みでした。特に背景画像が読み込まれるのが遅くて白画面になることが多かったです。自分自身の力でできる範囲でネットワークの読み込み速度を最大限に最適化しました(テーマを変更したり機能を削除したりしない前提で)。初回起動の速度はもう最速にしています。自分がコントロールできるリソースについてはすべてcache-control 応答ヘッダーを設定しましたが、2 回目の起動では画像やJSファイルなどがキャッシュされていないことがあり、例えば画像が一瞬白くなりその後で表示されるといった現象がありました。
今日はService Workerを弄って、ブログのアクセス速度が大幅に向上しました。なぜかというと、時折画像などの読み込みが急に遅くなることがありますけど、全体的に以前よりはるかに速くなりました。
Service Workerのコードをここに貼っておきます、参考にしてください。
const swconfig = {
CACHE_VERSION: "v2.0",
runtimeCaching: [
// {
// urlPattern: /^https:\/\/cdn\.example\.com\/.*/,
// handler: "CacheFirst",
// maxAgeSeconds: 60 * 60 * 24 * 365
// },
// {
// urlPattern: /https:\/\/blog.ksable.top\//gi,
// handler: "NetworkFirst",
// maxAgeSeconds: 60 * 60 * 24 * 7
// },
{
urlPattern: RegExp('^https://www.favicon.vip/get.php'),
handler: "CacheFirst",
maxAgeSeconds: 60 * 60 * 24 * 365
},
{
urlPattern: RegExp('^https://image.thum.io/'),
handler: "CacheFirst",
maxAgeSeconds: 60 * 60 * 24 * 30
},
{
urlPattern: /https?:\/\/[^\/]+\/.*\.(png|jpg|jpeg|gif|svg|ico|woff2|ttf|js|css?)(\?.*)?/,
handler: "CacheFirst",
maxAgeSeconds: 60 * 60 * 24 * 365
},
{
urlPattern: /^https:\/\/ik.imagekit.io\//,
handler: "CacheFirst",
maxAgeSeconds: 0
},
{
urlPattern: /^https:\/\/weavatar.com\/avatar\//,
handler: "CacheFirst",
maxAgeSeconds: 60 * 60 * 24 * 30
},
{
urlPattern: /^https:\/\/assets.ksable.top\/js\/my-js.js/,
handler: "CacheFirst",
maxAgeSeconds: 60 * 60 * 24 * 1
}
],
exclude: [
/^https:\/\/blog.ksable.top\/sw.js/gi
],
precacheUrls: [
'/content.json', // 预缓存的内容JSON文件
'/offline.html' // 离线页面
]
};
const CACHE_NAME = `${swconfig.CACHE_VERSION}-cache`;
const CACHE_META_KEY = 'cache-meta';
const OFFLINE_URL = '/offline.html';
// 匹配请求对应的规则
function matchRule(request) {
const url = request.url;
// 检查排除规则
for (const pattern of swconfig.exclude) {
if (pattern.test(url)) return null;
}
// 反向遍历确保后面的规则优先级更高
for (let i = swconfig.runtimeCaching.length - 1; i >= 0; i--) {
const rule = swconfig.runtimeCaching[i];
if (rule.urlPattern.test(url)) {
return rule;
}
}
return null;
}
// 获取缓存元数据
async function getCacheMeta() {
const cache = await caches.open(CACHE_NAME);
const response = await cache.match(CACHE_META_KEY);
return response ? await response.json() : {};
}
// 更新缓存元数据
async function updateCacheMeta(url, timestamp) {
const cache = await caches.open(CACHE_NAME);
const meta = await getCacheMeta();
meta[url] = {
timestamp,
cachedAt: Date.now()
};
await cache.put(
CACHE_META_KEY,
new Response(JSON.stringify(meta), {
headers: { 'Content-Type': 'application/json' }
})
);
}
// 后台更新缓存
async function backgroundUpdate(request, rule) {
try {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(request);
if (!cachedResponse) return;
// 准备验证头
const headers = new Headers();
const etag = cachedResponse.headers.get('ETag');
const lastModified = cachedResponse.headers.get('Last-Modified');
if (etag) headers.set('If-None-Match', etag);
if (lastModified) headers.set('If-Modified-Since', lastModified);
// 发送验证请求
const networkResponse = await fetch(request, {
headers,
cache: 'no-cache'
});
if (networkResponse.status === 304) { // 未修改
await updateCacheMeta(request.url, Date.now());
} else if (networkResponse.ok) { // 资源已更新
const responseClone = networkResponse.clone();
await cache.put(request, responseClone);
await updateCacheMeta(request.url, Date.now());
}
} catch (error) {
console.error('Background update failed:', error);
}
}
// 安装阶段 - 初始化缓存
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
// 初始化缓存元数据
return cache.put(
CACHE_META_KEY,
new Response('{}', {
headers: { 'Content-Type': 'application/json' }
})
).then(() => {
// 预缓存关键资源
return cache.addAll(swconfig.precacheUrls);
});
}).then(() => self.skipWaiting())
);
});
// 激活阶段 - 清理旧缓存
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(name => {
if (name !== CACHE_NAME && name.startsWith('v')) {
return caches.delete(name);
}
})
);
}).then(() => self.clients.claim())
);
});
// 请求处理
self.addEventListener('fetch', event => {
const request = event.request;
// 只处理GET请求
if (request.method !== 'GET') return;
// 特殊处理导航请求的离线回退
if (request.mode === 'navigate') {
event.respondWith(
(async () => {
try {
// 首先尝试网络请求
const networkResponse = await fetch(request);
return networkResponse;
} catch (error) {
// 网络失败时返回离线页面
const offlineResponse = await caches.match(OFFLINE_URL);
if (offlineResponse) {
return offlineResponse;
}
// 如果离线页面也未缓存,返回简单错误响应
return new Response('Offline', {
status: 503,
statusText: 'Service Unavailable'
});
}
})()
);
return;
}
// 匹配缓存规则
const rule = matchRule(request);
if (!rule) return;
// 处理不同缓存策略
switch (rule.handler) {
case 'NetworkOnly':
event.respondWith(fetch(request));
break;
case 'CacheOnly':
event.respondWith(
caches.match(request).then(response => response || Response.error())
);
break;
case 'NetworkFirst':
event.respondWith(
fetch(request).catch(() => caches.match(request))
);
break;
case 'CacheFirst':
default:
event.respondWith((async () => {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(request);
const meta = await getCacheMeta();
const url = request.url;
const metaEntry = meta[url];
// 如果找到缓存
if (cachedResponse) {
// 检查是否需要后台更新
if (rule.maxAgeSeconds > 0 && metaEntry) {
const age = (Date.now() - metaEntry.cachedAt) / 1000;
if (age > rule.maxAgeSeconds) {
// 后台更新不影响当前响应
event.waitUntil(backgroundUpdate(request, rule));
}
}
return cachedResponse;
}
// 没有缓存则请求网络
try {
const networkResponse = await fetch(request);
if (networkResponse.ok) {
const responseClone = networkResponse.clone();
await cache.put(request, responseClone);
await updateCacheMeta(url, Date.now());
}
return networkResponse;
} catch (error) {
return Response.error();
}
})());
break;
}
});
気に入ったならばコメントを残してくださいね~