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?
I understand that the reload prompt will still appear because the service worker enters a waiting state. However, this strategy ensures that if I make backend changes, the newly fetched JavaScript will work immediately instead of failing with API errors until the user manually reloads. This way, the app stays functional without waiting for the reload prompt to take effect.
Thanks