Make sw.js non-cacheable and self-update so the worker fix actually reaches clients
CI/CD / CI · dotnet build (push) Successful in 2m38s
CI/CD / Deploy · hamkadr (push) Successful in 3m30s

The sw.js file was cacheable (text/javascript, not covered by the HTML no-cache rule), so browsers/CDN kept serving the old v1 worker and never picked up the v2 fix. Serve sw.js with no-cache/no-store, call reg.update() on load, and reload once on controllerchange so stale cached pages are dropped immediately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-28 21:24:02 +03:30
parent f0e0b82375
commit 7acf94695f
2 changed files with 18 additions and 3 deletions
@@ -221,7 +221,16 @@
@* Register the PWA service worker (offline + push notifications). *@ @* Register the PWA service worker (offline + push notifications). *@
<script> <script>
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
window.addEventListener('load', function () { navigator.serviceWorker.register('/sw.js').catch(function () {}); }); window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js').then(function (reg) {
reg.update(); // always check for a newer worker so fixes reach clients fast
// When a new worker takes control, reload once so stale cached pages are dropped.
var refreshed = false;
navigator.serviceWorker.addEventListener('controllerchange', function () {
if (refreshed) return; refreshed = true; location.reload();
});
}).catch(function () {});
});
} }
</script> </script>
+8 -2
View File
@@ -335,7 +335,12 @@ app.MapPost("/like", async (HttpContext ctx, AppDbContext db, [FromForm] string
return Results.Json(new { liked, count }); return Results.Json(new { liked, count });
}).RequireAuthorization().DisableAntiforgery(); }).RequireAuthorization().DisableAntiforgery();
app.MapGet("/sw.js", () => Results.Content(""" app.MapGet("/sw.js", (HttpContext ctx) =>
{
// The worker file must NEVER be cached — otherwise an old worker keeps serving stale pages and
// browsers never pick up a new version (the fix can't reach clients because the file is cached).
ctx.Response.Headers.CacheControl = "no-cache, no-store, must-revalidate";
return Results.Content("""
const CACHE = 'hamkadr-v2'; const CACHE = 'hamkadr-v2';
const OFFLINE = '<!doctype html><html lang="fa" dir="rtl"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>آفلاین</title><body style="font-family:Vazirmatn,system-ui,sans-serif;text-align:center;padding:48px 20px;color:#334155"><h2 style="margin:0 0 8px">اتصال اینترنت برقرار نیست</h2><p style="color:#64748b">صفحه باز نشد؛ اتصال خود را بررسی و دوباره تلاش کنید.</p><button onclick="location.reload()" style="margin-top:14px;padding:10px 24px;border:0;border-radius:10px;background:#0d9488;color:#fff;font:inherit;cursor:pointer">تلاش دوباره</button></body></html>'; const OFFLINE = '<!doctype html><html lang="fa" dir="rtl"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>آفلاین</title><body style="font-family:Vazirmatn,system-ui,sans-serif;text-align:center;padding:48px 20px;color:#334155"><h2 style="margin:0 0 8px">اتصال اینترنت برقرار نیست</h2><p style="color:#64748b">صفحه باز نشد؛ اتصال خود را بررسی و دوباره تلاش کنید.</p><button onclick="location.reload()" style="margin-top:14px;padding:10px 24px;border:0;border-radius:10px;background:#0d9488;color:#fff;font:inherit;cursor:pointer">تلاش دوباره</button></body></html>';
self.addEventListener('install', e => { self.skipWaiting(); e.waitUntil(caches.open(CACHE).then(c => c.add('/'))); }); self.addEventListener('install', e => { self.skipWaiting(); e.waitUntil(caches.open(CACHE).then(c => c.add('/'))); });
@@ -368,7 +373,8 @@ self.addEventListener('notificationclick', e => {
const url = (e.notification.data && e.notification.data.url) || '/'; const url = (e.notification.data && e.notification.data.url) || '/';
e.waitUntil(clients.matchAll({ type: 'window' }).then(cl => { for (const c of cl) { if ('focus' in c) { c.navigate(url); return c.focus(); } } return clients.openWindow(url); })); e.waitUntil(clients.matchAll({ type: 'window' }).then(cl => { for (const c of cl) { if ('focus' in c) { c.navigate(url); return c.focus(); } } return clients.openWindow(url); }));
}); });
""", "text/javascript")); """, "text/javascript");
});
// ---- SEO: robots.txt + dynamic sitemap.xml (so Google indexes every live shift/job page) ---- // ---- SEO: robots.txt + dynamic sitemap.xml (so Google indexes every live shift/job page) ----
app.MapGet("/robots.txt", (HttpContext ctx) => app.MapGet("/robots.txt", (HttpContext ctx) =>