Reputation: 140
I'm trying to implement a network strategy for index.html
where it is always fetched from the network but falls back to the cache only when offline.
The reason for this approach is that my index.html
contains the entry points for the JavaScript and CSS files, which include a hash in their filenames. When I push an update, these hashes change, meaning the service worker won't find the old .js
and .css
files in the cache. By always fetching index.html
from the network, the app will instantly load the latest version without requiring a manual click to the reload prompt.
However, I still want the app to work offline, so if there's no internet connection, index.html
should be served from the cache.
I'm using vite-plugin-pwa
with injectManifest
, and my current service worker caches everything, including HTML, CSS, JS, PNG, ICO, and SVG files.
Here’s my initial service worker setup:
/// <reference lib="webworker" />
import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching';
import { NavigationRoute, registerRoute } from 'workbox-routing';
declare let self: ServiceWorkerGlobalScope;
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
});
// self.__WB_MANIFEST is the default injection point
precacheAndRoute(self.__WB_MANIFEST);
// clean old assets
cleanupOutdatedCaches();
registerRoute(new NavigationRoute(createHandlerBoundToURL('index.html')));
I’ve been trying to modify it so that index.html
is always fetched from the network, falling back to the cache only when offline. However, index.html
still seems to be served from the cache every time.
Here’s what I’ve tried:
/// <reference lib="webworker" />
import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching';
import { NavigationRoute, registerRoute } from 'workbox-routing';
import { NetworkFirst } from 'workbox-strategies';
import { cacheNames } from 'workbox-core';
declare let self: ServiceWorkerGlobalScope;
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
});
// self.__WB_MANIFEST is the default injection point
precacheAndRoute(self.__WB_MANIFEST);
// clean old assets
cleanupOutdatedCaches();
// Retrieve the precache cache name dynamically
const precacheCacheName = cacheNames.precache;
registerRoute(
({ request }) => request.mode === 'navigate' || request.url.endsWith('/index.html'),
new NetworkFirst({
cacheName: precacheCacheName,
plugins: [
{
fetchDidFail: async ({ request }) => {
console.warn('Network request failed, serving from cache:', request.url);
},
},
],
})
);
registerRoute(new NavigationRoute(createHandlerBoundToURL('index.html')));
What am I doing wrong?
Does my approach make sense fetching index.html
from the network to always serve the latest version of the app?
By not caching the index.html
, I eliminate the need for a reload prompt. The new service worker will activate once all tabs are closed. There's no need to call skipWaiting
or reload the current page, as the content will already be updated with the new index.html
.
Thanks
EDIT
I moved the registerRoute
above precacheAndRoute(self.__WB_MANIFEST)
, and now it seems to be working. In the console (only if I have "Preserve log upon navigation" enabled in the DevTools preferences), I see the following logs:
Network request failed, serving from cache: https://example.com/
Serving from cache: https://example.com/
Navigated to https://example.com/
When I update my app, it correctly fetches the new index.html
instead of the one from the cache.
However, I'm confused about why only
the index.html
is being served via a "network first" approach. I've added the condition request.mode === 'navigate' && request.url.endsWith('index.html')
, but with this change, it always loads from the cache index.html
and not the network first.
/// <reference lib="webworker" />
import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching';
import { NavigationRoute, registerRoute } from 'workbox-routing';
import { NetworkFirst } from 'workbox-strategies';
import { cacheNames } from 'workbox-core';
declare let self: ServiceWorkerGlobalScope;
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
});
// Retrieve the precache cache name dynamically
const precacheCacheName = cacheNames.precache;
registerRoute(
({ request }) => request.mode === 'navigate',
new NetworkFirst({
cacheName: precacheCacheName,
plugins: [
{
fetchDidFail: async ({ request }) => {
console.warn('Network request failed, serving from cache:', request.url);
const cachedResponse = await caches.match(request);
if (cachedResponse) {
console.log('Serving from cache:', request.url);
} else {
console.error('Cache miss for:', request.url);
}
},
},
],
})
);
// self.__WB_MANIFEST is the default injection point
precacheAndRoute(self.__WB_MANIFEST);
// clean old assets
cleanupOutdatedCaches();
registerRoute(new NavigationRoute(createHandlerBoundToURL('index.html')));
EDIT 2
I almost solved it with ({ request, url }) => request.mode === 'navigate' && url.pathname === '/'
.
The problem is now that on the very first page load precacheAndRoute(self.__WB_MANIFEST);
is not caching /
so it goes offline.
Upvotes: 0
Views: 41