Reliability/speed: warm CF cache on deploy + lighten SW (no precache storm)
The post-deploy blank/slow load: new hashed chunks weren't in Cloudflare yet, so the first visitor pulled them cold from the residential origin — AND the service worker simultaneously precached ~30 of those cold assets (a request storm), pushing past the 7s boot timeout. * sync-static.sh now warms the CF edge cache (fetches every immutable asset through the public domain) so the first visitor gets HITs, not cold-origin. * Service worker no longer bulk-precaches on install (the browser already caches immutable assets for a year); it caches the shell + assets lazily as used. No more storm. * Boot-recovery timeout 7s → 10s so a merely-slow load doesn't flash the card. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,3 +18,10 @@ rsync -a --delete \
|
||||
rsync -a "$src/index.html" "$site/index.html"
|
||||
rsync -a "$src/service-worker.js" "$site/service-worker.js"
|
||||
find "$site/_app/immutable" -type f -mtime +14 -delete 2>/dev/null || true
|
||||
|
||||
# Warm the Cloudflare edge cache: fetch every immutable asset through the public
|
||||
# domain so the FIRST real visitor after a deploy gets cache HITs instead of slow
|
||||
# cold fetches from the (residential) origin — the post-deploy blank/slow-load cause.
|
||||
echo " warming edge cache…"
|
||||
find "$site/_app/immutable" -type f \( -name '*.js' -o -name '*.css' \) -printf '/_app/immutable/%P\n' \
|
||||
| xargs -P 8 -I{} curl -fsS -o /dev/null --max-time 20 "https://upbeatbytes.com{}" 2>/dev/null || true
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
navigator.sendBeacon && navigator.sendBeacon('/api/events', b);
|
||||
} catch (e) { /* best-effort telemetry */ }
|
||||
}
|
||||
var timer = setTimeout(showBoot, 7000);
|
||||
var timer = setTimeout(showBoot, 10000);
|
||||
// Svelte calls this once it has mounted (see +layout.svelte).
|
||||
window.__ubBooted = function () {
|
||||
window.__ubMounted = true;
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
// Calm service worker: precache the app shell + static assets so the site is
|
||||
// installable (PWA), fast, and resilient to transient network blips. Live data
|
||||
// (the API and server-rendered pages) is always fetched fresh, never cached.
|
||||
import { build, files, version } from '$service-worker';
|
||||
// Calm, lightweight service worker. It does NOT bulk-precache on install — the
|
||||
// browser already caches the year-immutable assets on its own, and a cold
|
||||
// precache storm right after a deploy hammers the (residential) origin and slows
|
||||
// first loads. Instead: cache the shell for an offline fallback, and cache other
|
||||
// assets lazily as they're actually used. Live data (API + server-rendered pages)
|
||||
// is always fetched fresh.
|
||||
import { version } from '$service-worker';
|
||||
|
||||
const CACHE = `upbeat-${version}`;
|
||||
const SHELL = [...build, ...files];
|
||||
|
||||
// Paths the FastAPI server owns — the SW must NOT intercept or cache these
|
||||
// (matches Caddy's @api matcher: docs, server-rendered article/brief pages,
|
||||
// health, sitemap). Everything else is the static SvelteKit SPA.
|
||||
// Paths the FastAPI server owns — the SW must NOT intercept or cache these.
|
||||
function isServerPath(p) {
|
||||
if (p.startsWith('/api/') || p.startsWith('/a/') || p.startsWith('/docs')) return true;
|
||||
return p === '/openapi.json' || p === '/healthz' || p === '/today' || p === '/sitemap.xml';
|
||||
}
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
// Best-effort: grab the app shell as an offline fallback. No bulk precache.
|
||||
event.waitUntil(
|
||||
caches.open(CACHE).then(async (c) => {
|
||||
await c.addAll(SHELL);
|
||||
// Cache the SPA shell so a failed navigation has a real page to fall back
|
||||
// to (best-effort; it's also refreshed on every successful navigation).
|
||||
try { await c.add('/'); } catch { /* refreshed on first online navigation */ }
|
||||
await self.skipWaiting();
|
||||
})
|
||||
caches.open(CACHE).then((c) => c.add('/').catch(() => {})).then(() => self.skipWaiting())
|
||||
);
|
||||
});
|
||||
|
||||
@@ -43,8 +38,8 @@ self.addEventListener('fetch', (event) => {
|
||||
if (url.origin !== location.origin) return;
|
||||
if (isServerPath(url.pathname)) return; // let the network/server handle these
|
||||
|
||||
// Navigations: network-first. On success, keep the freshest *real HTML shell*
|
||||
// as the offline fallback; on failure, serve that cached shell (never blank).
|
||||
// Navigations: network-first; keep the freshest real HTML shell as the offline
|
||||
// fallback; on a failed fetch, serve that cached shell (never blank).
|
||||
if (request.mode === 'navigate') {
|
||||
event.respondWith(
|
||||
fetch(request)
|
||||
@@ -60,6 +55,18 @@ self.addEventListener('fetch', (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Static assets (hashed JS/CSS, fonts, icons, word lists): cache-first.
|
||||
event.respondWith(caches.match(request).then((cached) => cached || fetch(request)));
|
||||
// Static assets: serve from cache if present, else fetch and cache for next time
|
||||
// (cache-as-you-go — no install storm). Only cache successful same-origin GETs.
|
||||
event.respondWith(
|
||||
caches.match(request).then((cached) => {
|
||||
if (cached) return cached;
|
||||
return fetch(request).then((res) => {
|
||||
if (res && res.ok && res.type === 'basic') {
|
||||
const copy = res.clone();
|
||||
caches.open(CACHE).then((c) => c.put(request, copy)).catch(() => {});
|
||||
}
|
||||
return res;
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user