fix(offline): fall back to cached shell on upstream 5xx
Service worker previously only fell back to cache when fetch threw (network unreachable). A 502/503/504 from the origin returned successfully with !response.ok, so the bad page was passed through to the user. Now upstream 5xx is treated like a network failure: try cached page, then offline-shell redirect for recipe routes, then the styled offline page. 4xx still passes through unchanged.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.67.3",
|
"version": "1.67.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+37
-25
@@ -117,30 +117,34 @@ sw.addEventListener('fetch', (event) => {
|
|||||||
// SvelteKit adds ?x-sveltekit-invalidated=... which we need to ignore
|
// SvelteKit adds ?x-sveltekit-invalidated=... which we need to ignore
|
||||||
const cacheKey = url.pathname;
|
const cacheKey = url.pathname;
|
||||||
|
|
||||||
|
let response: Response | undefined;
|
||||||
try {
|
try {
|
||||||
// Try network first
|
response = await fetch(event.request);
|
||||||
const response = await fetch(event.request);
|
} catch {
|
||||||
|
// Network unreachable — fall through to cache fallback below.
|
||||||
|
}
|
||||||
|
|
||||||
// Cache successful responses for offline use (using pathname as key)
|
// Cache successful responses for offline use (using pathname as key)
|
||||||
if (response.ok) {
|
if (response?.ok) {
|
||||||
cache.put(cacheKey, response.clone());
|
cache.put(cacheKey, response.clone());
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch {
|
|
||||||
// Network failed - try to serve from cache (ignoring query params)
|
|
||||||
const cached = await cache.match(cacheKey);
|
|
||||||
if (cached) {
|
|
||||||
return cached;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No cached data available - return error response
|
// Network unreachable OR upstream 5xx (502/503/504 etc.) — serve
|
||||||
// The page will need to handle this gracefully
|
// stale cached data so the PWA stays usable when the origin is down.
|
||||||
|
if (!response || response.status >= 500) {
|
||||||
|
const cached = await cache.match(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass through non-5xx errors (404, 401, ...) untouched.
|
||||||
|
if (response) return response;
|
||||||
|
|
||||||
|
// No response and no cache — synthetic offline error.
|
||||||
return new Response(JSON.stringify({ error: 'offline' }), {
|
return new Response(JSON.stringify({ error: 'offline' }), {
|
||||||
status: 503,
|
status: 503,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
}
|
|
||||||
})()
|
})()
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -222,27 +226,32 @@ sw.addEventListener('fetch', (event) => {
|
|||||||
// Use pathname only for cache key (ignore query params)
|
// Use pathname only for cache key (ignore query params)
|
||||||
const cacheKey = url.pathname;
|
const cacheKey = url.pathname;
|
||||||
|
|
||||||
|
let response: Response | undefined;
|
||||||
try {
|
try {
|
||||||
// Try network first
|
response = await fetch(event.request);
|
||||||
const response = await fetch(event.request);
|
} catch {
|
||||||
|
// Network unreachable — fall through to fallback below.
|
||||||
|
}
|
||||||
|
|
||||||
// Cache successful HTML responses for cacheable pages (using pathname as key)
|
// Cache successful HTML responses for cacheable pages (using pathname as key)
|
||||||
const isCacheablePage = response.ok && (
|
if (response?.ok) {
|
||||||
|
const isCacheablePage =
|
||||||
url.pathname.match(/^\/(rezepte|recipes|glaube|faith|fitness)(\/|$)/) ||
|
url.pathname.match(/^\/(rezepte|recipes|glaube|faith|fitness)(\/|$)/) ||
|
||||||
url.pathname === '/'
|
url.pathname === '/';
|
||||||
);
|
|
||||||
if (isCacheablePage) {
|
if (isCacheablePage) {
|
||||||
cache.put(cacheKey, response.clone());
|
cache.put(cacheKey, response.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch {
|
|
||||||
// Network failed - try to serve from cache (ignoring query params)
|
|
||||||
const cached = await cache.match(cacheKey);
|
|
||||||
if (cached) {
|
|
||||||
return cached;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Network unreachable OR upstream 5xx (502 Bad Gateway, 503, 504, ...) —
|
||||||
|
// serve stale shell so the PWA stays usable when the origin is down.
|
||||||
|
const upstreamDown = !response || response.status >= 500;
|
||||||
|
|
||||||
|
if (upstreamDown) {
|
||||||
|
const cached = await cache.match(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
// For recipe routes, redirect to the offline shell with the target URL
|
// For recipe routes, redirect to the offline shell with the target URL
|
||||||
// The offline shell will then do client-side navigation to load from IndexedDB
|
// The offline shell will then do client-side navigation to load from IndexedDB
|
||||||
// Skip if this is already the offline-shell or an offline navigation to prevent loops
|
// Skip if this is already the offline-shell or an offline navigation to prevent loops
|
||||||
@@ -262,6 +271,10 @@ sw.addEventListener('fetch', (event) => {
|
|||||||
return Response.redirect(redirectUrl, 302);
|
return Response.redirect(redirectUrl, 302);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass through non-5xx errors (404, 401, ...) untouched.
|
||||||
|
if (response && !upstreamDown) return response;
|
||||||
|
|
||||||
// Last resort - return a styled offline response
|
// Last resort - return a styled offline response
|
||||||
return new Response(
|
return new Response(
|
||||||
@@ -299,7 +312,6 @@ p{color:#aaa}
|
|||||||
</body></html>`,
|
</body></html>`,
|
||||||
{ headers: { 'Content-Type': 'text/html' } }
|
{ headers: { 'Content-Type': 'text/html' } }
|
||||||
);
|
);
|
||||||
}
|
|
||||||
})()
|
})()
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user