雑記: 2025年7月5日

公開日: 2025-07-05 15:45 更新日: 2025-07-05 17:16 1288文字 7 min read

碎碎念: 期末 · C碎碎念: 期末 · A雑記: 最近のいくつかの技術の進展に関するまとめつぶやき: 今日は私の誕生日ですちびちび言葉: 高3の成人式雑感: 26-02-25ススメン: 26-02-22雑談: 26-02-20つぶやき: 26-02-18-2日記: 2018年2月26日ススネン: 2017年2月26日つぶやき: 2015年2月26日雑談: 2014年2月26日雑念: 26-01-16 友人、人間関係碎碎念: 26-01-01 2025年,年度回顾雑談: 2025年10月19日雑念: 2025-10-17 (補)碎碎念: 2025-10-13 (补、密码保护)碎碎念: ごめんなさい、内向的な私を許してくださいつらつら: 2025-09-23雑感: 完全移植 PureSuckテーマ雑記: 2025年7月16日雑記: 2025年7月5日雑念: 2025年6月13日ちちち: 2025-06-09つぶやき: 2025年5月18日 夕方の一枚の写真つぶやき: 2025-05-05つれづれな話: 2025年4月30日雑談: 2025年4月20日ススリナイ: 2025-04-19くちゃくちゃ: 2025年4月13日散らばった呟き: 2025年3月9日-2ちちねん: 2025-03-09つぶやき: 私はとても怠けているので、年末のまとめすら書けていません。つぶやき: 2024年11月17日つらつら: 2024-10-8 〜 2024-10-18つぶやき: 2024年9月29日つぶやき: 2024-09-24雑感: 2024年9月23日雑感: 2024年10月5日碎碎念: 2024年10月3日 芋泥波波ミルクティーを飲んだちちちん: 2024年10月3日 世界に永遠の戦争がなくあれススメン: 2024年9月15日碎碎念: 2024-09-01つぶやき: 2024-08-29 誰が私のプレイリストをいじったの?碎碎念: 2024-08-29 我抑郁症?ささやき: 2024年8月29日ちちねん: 2024-08-26ススメン: 2024-08-22つらつらメモ: 2024年8月18日ススメン: 2024年8月11日ちちねん: 2024-08-08碎碎念: 2024-08-06 梦雑念: 2024年8月6日雑談: 2024-08-04つらつら: 2024年7月21日雑談: 2024-07-13つぶやき: 2024年7月8日雑談: 2024-07-03雑念: 2024年7月2日雑談: 2024年7月1日つぶやき: 2024-06-30碎碎念: 2024年6月28日ちぎりばっかの言葉: 2024-06-27碎碎念: 2024-06-26ちちねん: 2024年6月22日碎碎念: 2024-06-20ちちねん: 2024-06-18つれづれな話: 2024年6月17日日記: 2024年6月15日ちちねん: 2024年6月14日つぶやき: 2024年の高校3年生の呼び声碎碎念: 2024-06-06つぶやき: 2024年5月30日 一碎碎念: 2024年5月30日 二つれづれなること: 2024-05-27つぶやき: 2024年5月26日碎碎念: 2024-05-23碎碎念: 2024-05-22つぶやき: 2024-05-19碎碎念: 2024-05-17つぶやき: 2024年5月14日つぶやき: 2024年5月13日ちちねん: 2024-05-12碎碎念: 2024-05-10碎碎念: 2024-05-08碎碎念: 2024-05-06碎碎念: 2024-05-05 M:8 三碎碎念: 2024-05-05碎碎念: 2024-05-05 M:7 二すいすいねん: 2024年5月5日 M:7 一雑談: 2024年5月2日 木曜日 M:3 小雨碎碎念: 生日つぶやき: 2024-04-29 M:7日記: 2024年4月27日 M:8
半日かけてようやくサイトのService Workerのキャッシュ設定を整えました。以前はいつもサイトの読み込みが遅く、何秒も待たないと表示終わらず、背景画像も少し滞って読み込まれていました。自分ができる範囲でネットワークの読み込み速度を最大限に引き上げましたし、最初の読み込みは最速にしています。自分が管理できるリソースについてはすべてキャッシュコントロール応答ヘッダーを設定しましたが、二回目の起動時には画像やJSファイルがキャッシュされず、例えば画像が一瞬白くなるなどの現象がありました。今日はService Worker...

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;
    }
});

気に入ったならばコメントを残してくださいね~