Skip to content

Commit 6835d03

Browse files
Copexitclaude
andcommitted
fix: switch service worker to network-first for HTML
Cache-first was serving stale index.html after deploys, causing chunk 404s and "Application error" for all users until hard refresh. Now HTML always fetches from network (offline falls back to cache), while content-hashed _next/static/* assets stay cache-first. Bumps cache name to v2 to evict broken v1 cache for existing users. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 12ae363 commit 6835d03

File tree

1 file changed

+31
-27
lines changed

1 file changed

+31
-27
lines changed

public/sw.js

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
const CACHE_NAME = "ami-exposed-v1";
1+
const CACHE_NAME = "ami-exposed-v2";
22
const OFFLINE_URL = "/offline.html";
33

4-
const PRECACHE_URLS = ["/", "/offline.html"];
5-
64
self.addEventListener("install", (event) => {
75
event.waitUntil(
86
caches
97
.open(CACHE_NAME)
10-
.then((cache) => cache.addAll(PRECACHE_URLS))
8+
.then((cache) => cache.addAll([OFFLINE_URL]))
119
.then(() => self.skipWaiting()),
1210
);
1311
});
@@ -28,42 +26,48 @@ self.addEventListener("activate", (event) => {
2826
});
2927

3028
self.addEventListener("fetch", (event) => {
31-
// Only handle GET requests
3229
if (event.request.method !== "GET") return;
3330

34-
// Skip all cross-origin requests (API calls, custom endpoints, etc.)
3531
const url = new URL(event.request.url);
36-
if (url.origin !== self.location.origin) {
37-
return;
38-
}
3932

40-
event.respondWith(
41-
caches.match(event.request).then((cached) => {
42-
if (cached) return cached;
33+
// Skip cross-origin requests (API calls, custom endpoints, etc.)
34+
if (url.origin !== self.location.origin) return;
4335

44-
return fetch(event.request)
45-
.then((response) => {
46-
// Cache successful responses for static assets
47-
if (
48-
response.ok &&
49-
(url.pathname.endsWith(".js") ||
50-
url.pathname.endsWith(".css") ||
51-
url.pathname.endsWith(".svg") ||
52-
url.pathname.endsWith(".woff2") ||
53-
url.pathname.endsWith(".json"))
54-
) {
36+
// Content-hashed assets (_next/static/*) are immutable - cache-first is safe
37+
if (url.pathname.startsWith("/_next/static/")) {
38+
event.respondWith(
39+
caches.match(event.request).then((cached) => {
40+
if (cached) return cached;
41+
return fetch(event.request).then((response) => {
42+
if (response.ok) {
5543
const clone = response.clone();
5644
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
5745
}
5846
return response;
59-
})
60-
.catch(() => {
61-
// Offline fallback for navigation requests
47+
});
48+
}),
49+
);
50+
return;
51+
}
52+
53+
// HTML and everything else: network-first, fall back to cache
54+
event.respondWith(
55+
fetch(event.request)
56+
.then((response) => {
57+
if (response.ok) {
58+
const clone = response.clone();
59+
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
60+
}
61+
return response;
62+
})
63+
.catch(() => {
64+
return caches.match(event.request).then((cached) => {
65+
if (cached) return cached;
6266
if (event.request.mode === "navigate") {
6367
return caches.match(OFFLINE_URL);
6468
}
6569
return new Response("Offline", { status: 503 });
6670
});
67-
}),
71+
}),
6872
);
6973
});

0 commit comments

Comments
 (0)