Files
cannamanage/cannamanage-frontend/public/sw.js
T
Patrick Plate 4be9c4cf2c
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
fix(frontend): resolve app-wide 'Oops' crash + PWA middleware interception
Root cause (found via Playwright browser probe — curl could not detect client-side
hydration errors):

1. ROOT-LAYOUT INTL CRASH (the 'Oops' on every page incl /login):
   app/layout.tsx renders global client components (PwaInstallPrompt → useTranslations,
   Toaster, Sonner) as siblings of {children} inside <Providers>, but only each
   route-group layout wrapped its own children in NextIntlClientProvider. So those
   global components mounted with NO intl context → 'No intl context found' → React
   hydration crash → global-error 'Oops'. Fix: wrap the root body in
   NextIntlClientProvider via getMessages() (RootLayout now async). Nested providers
   stay valid (next-intl supports nesting).

2. PWA MIDDLEWARE INTERCEPTION (manifest.json syntax error + stale cache):
   middleware matcher did not exclude /manifest.json or /sw.js, so unauthenticated
   browsers got 307→/login (HTML) for both. Browser parsed HTML as JSON
   ('manifest.json:1 Syntax error') and an HTML/old service worker kept serving
   stale bundles ('website hasn't changed' after redeploys). Fix: exclude
   manifest.json, sw.js, icons, offline from the matcher.

3. SERVICE-WORKER STALE CACHE: bump CACHE_NAME v1→v2 so the activate handler purges
   old cached bundles from clients that loaded the broken build.

Also adds scripts/debug/dashboard-probe.mjs — a Playwright probe that logs in and
captures real client-side console/network errors + screenshot.
2026-06-13 10:36:09 +02:00

77 lines
2.1 KiB
JavaScript

/// <reference lib="webworker" />
// Bump this version on every release that changes cached assets. The `activate`
// handler below deletes all caches whose name !== CACHE_NAME, so incrementing
// this string force-purges stale bundles from clients that cached the old
// (broken) build — fixes "website hasn't changed after redeploy".
const CACHE_NAME = "cannamanage-v2"
const OFFLINE_URL = "/offline"
// Assets to pre-cache
const PRECACHE_ASSETS = [
"/offline",
"/manifest.json",
"/icons/icon-192.png",
"/icons/icon-512.png",
]
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(PRECACHE_ASSETS)
})
)
self.skipWaiting()
})
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
)
})
)
self.clients.claim()
})
self.addEventListener("fetch", (event) => {
// Only handle GET requests
if (event.request.method !== "GET") return
// Skip API requests — let them fail naturally
if (event.request.url.includes("/api/")) return
// Network-first for navigation requests
if (event.request.mode === "navigate") {
event.respondWith(
fetch(event.request).catch(() => {
return caches.match(OFFLINE_URL)
})
)
return
}
// Stale-while-revalidate for static assets
if (
event.request.destination === "style" ||
event.request.destination === "script" ||
event.request.destination === "image"
) {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
if (networkResponse && networkResponse.status === 200) {
const cache = caches.open(CACHE_NAME)
cache.then((c) => c.put(event.request, networkResponse.clone()))
}
return networkResponse
})
return cachedResponse || fetchPromise
})
)
}
})