fix: stale SW chunks serving HTML, PWA cache cleanup, server asset routing
- Server SPA fallback now returns 404 for missing .js/.css assets instead of index.html - Prevents "Unexpected token '<'" errors from stale chunk URLs - Serve PWA files (sw.js, workbox, icons) with correct cache headers - Workbox: cleanupOutdatedCaches, skipWaiting, clientsClaim for instant SW updates - navigateFallbackDenylist for /api/ routes - AudioContext comment clarification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
20e9f0f7a2
commit
7d5f4a6b8d
@@ -5,6 +5,9 @@ let sfxGain: GainNode | null = null
|
||||
let musicPlaying = false
|
||||
let musicTimeout: number | null = null
|
||||
|
||||
// Lazily create AudioContext + gain nodes on first use.
|
||||
// Chrome may warn about autoplay policy but the context will resume once ensureAudioContext()
|
||||
// is called from a user gesture. All SFX calls before that are queued/silent.
|
||||
function getCtx(): AudioContext {
|
||||
if (!ctx) {
|
||||
ctx = new AudioContext()
|
||||
|
||||
@@ -28,7 +28,11 @@ export default defineConfig({
|
||||
},
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,svg,png,woff,woff2}'],
|
||||
cleanupOutdatedCaches: true,
|
||||
skipWaiting: true,
|
||||
clientsClaim: true,
|
||||
navigateFallback: '/index.html',
|
||||
navigateFallbackDenylist: [/^\/api\//],
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
|
||||
|
||||
+13
-1
@@ -75,9 +75,21 @@ if (process.env.NODE_ENV === 'production' && existsSync(publicDir)) {
|
||||
app.get('/robots.txt', (c) => serveFile(c, '/robots.txt', 'public, max-age=86400'))
|
||||
app.get('/manifest.webmanifest', (c) => serveFile(c, '/manifest.webmanifest', 'public, max-age=86400'))
|
||||
|
||||
// SPA fallback: all non-API routes serve index.html
|
||||
// PWA service worker + related root files
|
||||
app.get('/sw.js', (c) => serveFile(c, '/sw.js', 'no-cache'))
|
||||
app.get('/workbox-*.js', (c) => serveFile(c, c.req.path, 'public, max-age=31536000, immutable'))
|
||||
app.get('/icon-*.png', (c) => serveFile(c, c.req.path, 'public, max-age=86400'))
|
||||
app.get('/icon.svg', (c) => serveFile(c, '/icon.svg', 'public, max-age=86400'))
|
||||
app.get('/apple-touch-icon.png', (c) => serveFile(c, '/apple-touch-icon.png', 'public, max-age=86400'))
|
||||
|
||||
// SPA fallback: only for navigation requests (not JS/CSS/asset files)
|
||||
app.get('*', (c) => {
|
||||
if (c.req.path.startsWith('/api/')) return c.notFound()
|
||||
// Don't serve index.html for asset requests — return 404 so the browser gets a proper error
|
||||
const ext = c.req.path.split('.').pop()
|
||||
if (ext && ext !== c.req.path && ['js', 'css', 'map', 'json', 'png', 'jpg', 'svg', 'woff', 'woff2', 'webp', 'ico'].includes(ext)) {
|
||||
return c.notFound()
|
||||
}
|
||||
const indexPath = join(publicDir, 'index.html')
|
||||
c.header('Content-Type', 'text/html')
|
||||
c.header('Cache-Control', 'no-cache')
|
||||
|
||||
Reference in New Issue
Block a user