From 7181b55d52d7ab627379858a69e589baa6179d8b Mon Sep 17 00:00:00 2001 From: Padreug Date: Mon, 27 Apr 2026 19:48:45 +0200 Subject: [PATCH] fix: scope PWA service workers to prevent cross-app interference When multiple SPAs share the same origin (e.g., /sortir/ and /castle/ on demo.aiolabs.dev), their service workers conflict. Each app's workbox now scopes its navigateFallback with navigateFallbackAllowlist, and the main app excludes standalone paths via navigateFallbackDenylist. - Main app: denylist /sortir/ and /castle/ from its service worker - Sortir: allowlist only /sortir/ paths, fallback to activities.html - Castle: allowlist only /castle/ paths, fallback to castle.html - Icon paths use relative URLs (work with any base path) Co-Authored-By: Claude Opus 4.6 (1M context) --- vite.activities.config.ts | 13 +++++++++---- vite.castle.config.ts | 14 +++++++++----- vite.config.ts | 4 +++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/vite.activities.config.ts b/vite.activities.config.ts index c7a4de8..59ad437 100644 --- a/vite.activities.config.ts +++ b/vite.activities.config.ts @@ -51,6 +51,11 @@ export default defineConfig(({ mode }) => ({ }, workbox: { globPatterns: ['**/*.{js,css,html,ico,png,svg}'], + // Scope the service worker to only handle requests within this app's path + navigateFallback: 'activities.html', + navigateFallbackAllowlist: [ + new RegExp(`^${(process.env.VITE_BASE_PATH || '/').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`), + ], }, includeAssets: [ 'favicon.ico', @@ -75,10 +80,10 @@ export default defineConfig(({ mode }) => ({ categories: ['social', 'entertainment', 'lifestyle'], lang: 'fr', icons: [ - { src: '/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' }, - { src: '/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any' }, - { src: '/icon-maskable-192.png', sizes: '192x192', type: 'image/png', purpose: 'maskable' }, - { src: '/icon-maskable-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' }, + { src: 'icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' }, + { src: 'icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any' }, + { src: 'icon-maskable-192.png', sizes: '192x192', type: 'image/png', purpose: 'maskable' }, + { src: 'icon-maskable-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' }, ], }, }), diff --git a/vite.castle.config.ts b/vite.castle.config.ts index 4d928c2..c16c2a2 100644 --- a/vite.castle.config.ts +++ b/vite.castle.config.ts @@ -51,6 +51,10 @@ export default defineConfig(({ mode }) => ({ }, workbox: { globPatterns: ['**/*.{js,css,html,ico,png,svg}'], + navigateFallback: 'castle.html', + navigateFallbackAllowlist: [ + new RegExp(`^${(process.env.VITE_BASE_PATH || '/').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`), + ], }, includeAssets: [ 'favicon.ico', @@ -70,15 +74,15 @@ export default defineConfig(({ mode }) => ({ display: 'standalone', orientation: 'portrait-primary', start_url: process.env.VITE_BASE_PATH || '/', - scope: '/', + scope: process.env.VITE_BASE_PATH || '/', id: 'castle-accounting', categories: ['finance', 'business', 'productivity'], lang: 'en', icons: [ - { src: '/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' }, - { src: '/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any' }, - { src: '/icon-maskable-192.png', sizes: '192x192', type: 'image/png', purpose: 'maskable' }, - { src: '/icon-maskable-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' }, + { src: 'icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' }, + { src: 'icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any' }, + { src: 'icon-maskable-192.png', sizes: '192x192', type: 'image/png', purpose: 'maskable' }, + { src: 'icon-maskable-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' }, ], }, }), diff --git a/vite.config.ts b/vite.config.ts index 669900a..fee1c90 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -23,7 +23,9 @@ export default defineConfig(({ mode }) => ({ workbox: { globPatterns: [ '**/*.{js,css,html,ico,png,svg}' - ] + ], + // Don't intercept standalone app paths — they have their own service workers + navigateFallbackDenylist: [/^\/sortir\//, /^\/castle\//], }, includeAssets: [ 'favicon.ico',