Fix stale queue page: switch pages to network-first in service worker

The service worker was using cache-first for ALL non-API routes,
including page routes like /queue, /capture, /browser. This meant
the browser kept serving old cached HTML with old inline JS even
after deploys, which is why the queue file list never updated
(old JS was running, silently crashing on API mismatches).

Now only /static/ assets use cache-first (they're versioned via
SW cache bumps). All pages and API calls use network-first with
cache as offline fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 20:57:54 -06:00
parent 78c3c54a26
commit 70562e1d2b

View File

@@ -1,9 +1,9 @@
// NextSnap Service Worker
// Provides offline-first caching for the app shell
const CACHE_VERSION = 'nextsnap-v18';
const APP_SHELL_CACHE = 'nextsnap-shell-v14';
const RUNTIME_CACHE = 'nextsnap-runtime-v14';
const CACHE_VERSION = 'nextsnap-v19';
const APP_SHELL_CACHE = 'nextsnap-shell-v15';
const RUNTIME_CACHE = 'nextsnap-runtime-v15';
// Assets to cache on install
const APP_SHELL_ASSETS = [
@@ -62,91 +62,72 @@ self.addEventListener('activate', (event) => {
);
});
// Fetch event - cache-first for static assets, network-first for API
// Fetch event - route requests to appropriate caching strategy
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Skip non-GET requests
// Skip non-GET requests (uploads, form posts, etc.)
if (request.method !== 'GET') {
return;
}
// API requests - network-first with offline fallback
if (url.pathname.startsWith('/api/')) {
event.respondWith(
fetch(request)
.then((response) => {
// Clone response for cache
const responseClone = response.clone();
// Only cache successful responses (not errors or auth failures)
if (response.status === 200) {
caches.open(RUNTIME_CACHE).then((cache) => {
// Don't cache file uploads or large responses
if (!url.pathname.includes('/upload') &&
!url.pathname.includes('/thumbnail')) {
cache.put(request, responseClone);
}
});
}
return response;
})
.catch(() => {
// Network failed - try cache, then offline response
return caches.match(request)
.then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
// Return offline fallback for API
return new Response(
JSON.stringify({
error: 'offline',
message: 'You are offline. This feature requires connectivity.'
}),
{
status: 503,
statusText: 'Service Unavailable',
headers: { 'Content-Type': 'application/json' }
}
);
});
})
);
return;
}
// Static assets - cache-first strategy
// Static assets (/static/) - cache-first (versioned via SW cache bump)
if (url.pathname.startsWith('/static/')) {
event.respondWith(
caches.match(request)
.then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
// Not in cache - fetch from network and cache it
return fetch(request)
.then((response) => {
// Don't cache non-successful responses
if (!response || response.status !== 200 || response.type === 'error') {
if (!response || response.status !== 200) {
return response;
}
// Clone response for cache
const responseClone = response.clone();
caches.open(RUNTIME_CACHE).then((cache) => {
caches.open(APP_SHELL_CACHE).then((cache) => {
cache.put(request, responseClone);
});
return response;
})
.catch(() => new Response('Offline', { status: 503 }));
})
);
return;
}
// Everything else (pages, API) - network-first with cache fallback
event.respondWith(
fetch(request)
.then((response) => {
// Cache successful GET responses for offline fallback
if (response.status === 200) {
const responseClone = response.clone();
caches.open(RUNTIME_CACHE).then((cache) => {
// Don't cache file uploads or large binary responses
if (!url.pathname.includes('/upload') &&
!url.pathname.includes('/thumbnail')) {
cache.put(request, responseClone);
}
});
}
return response;
})
.catch(() => {
// Network failed and not in cache
// For HTML pages, could return offline page here
// Network failed - try cache
return caches.match(request)
.then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
// No cache - return offline response
if (url.pathname.startsWith('/api/')) {
return new Response(
JSON.stringify({ error: 'offline', message: 'You are offline.' }),
{ status: 503, headers: { 'Content-Type': 'application/json' } }
);
}
return new Response('Offline', { status: 503 });
});
})