diff --git a/docker-compose.app.yml b/docker-compose.app.yml index 6b63af7e..5fd85d50 100644 --- a/docker-compose.app.yml +++ b/docker-compose.app.yml @@ -14,7 +14,6 @@ # NOTE: Images must be built separately before using: # cd /data/app/igny8/backend && docker build -t igny8-backend:latest -f Dockerfile . # cd /data/app/igny8/frontend && docker build -t igny8-frontend-dev:latest -f Dockerfile.dev . -# cd /data/app/igny8/frontend && docker build -t igny8-marketing:latest -f Dockerfile.marketing . # cd /data/app/igny8/frontend && docker build -t igny8-marketing-dev:latest -f Dockerfile.marketing.dev . # ============================================================================= @@ -83,25 +82,9 @@ services: - "com.docker.compose.project=igny8-app" - "com.docker.compose.service=igny8_frontend" - igny8_marketing: - # NOTE: Use 'image:' not 'build:' to avoid creating parallel stacks - # Build images separately: docker build -t igny8-marketing:latest -f Dockerfile.marketing . - # NOTE: This can run in parallel with igny8_marketing_dev - they use different ports - # Production build accessible at http://localhost:8022 (direct) or via Caddy routing - image: igny8-marketing:latest - container_name: igny8_marketing - restart: always - ports: - - "0.0.0.0:8022:8020" # Marketing site port (internal: 8020 Caddy, external: 8022) - networks: [igny8_net] - labels: - - "com.docker.compose.project=igny8-app" - - "com.docker.compose.service=igny8_marketing" - igny8_marketing_dev: # Development server for marketing site with HMR # Build separately: docker build -t igny8-marketing-dev:latest -f Dockerfile.marketing.dev . - # NOTE: This runs in parallel with igny8_marketing - they use different ports (no conflict) # Dev server accessible at http://localhost:8023 (direct) or via Caddy routing (when configured) image: igny8-marketing-dev:latest container_name: igny8_marketing_dev diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 3fac38d8..78728637 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -40,18 +40,42 @@ const bypassHostCheck = () => { }; }; -// Plugin to serve marketing.html at root for marketing dev server -const serveMarketingAtRoot = () => { +// Plugin to serve marketing.html for SPA routing in marketing dev server +const serveMarketingSPA = () => { return { - name: 'serve-marketing-at-root', + name: 'serve-marketing-spa', configureServer(server) { - server.middlewares.use((req, res, next) => { - // If on port 5174 and requesting root, serve marketing.html - if (server.config.server.port === 5174 && req.url === '/') { + const isMarketingDev = server.config.server.port === 5174; + + if (isMarketingDev) { + // Handle SPA routing: serve marketing.html for all routes that don't match actual files + server.middlewares.use((req, res, next) => { + const url = req.url?.split('?')[0] || '/'; // Remove query string + + // Skip for Vite internal paths and static assets + if ( + url.startsWith('/@') || // Vite internal (@vite/client, @fs, etc.) + url.startsWith('/node_modules') || // Node modules + url.startsWith('/src') || // Source files + url.startsWith('/api') || // API calls + url === '/marketing.html' // Already the marketing HTML + ) { + return next(); + } + + // Check if URL has a file extension (static asset) + const hasFileExtension = /\.\w+$/.test(url); + if (hasFileExtension) { + return next(); // Let Vite handle static assets + } + + // For all route-like paths (no extension), serve marketing.html + // This enables SPA routing for direct URL access (e.g., /product, /pricing) + // and browser back/forward navigation req.url = '/marketing.html'; - } - next(); - }); + next(); + }); + } } }; }; @@ -71,7 +95,7 @@ export default defineConfig(({ mode, command }) => { publicDir: "public", plugins: [ bypassHostCheck(), // Workaround for Vite 6.1.0 host check bug - ...(process.env.PORT === "5174" || process.argv.includes("--port") && process.argv[process.argv.indexOf("--port") + 1] === "5174" ? [serveMarketingAtRoot()] : []), + ...(process.env.PORT === "5174" || process.argv.includes("--port") && process.argv[process.argv.indexOf("--port") + 1] === "5174" ? [serveMarketingSPA()] : []), react(), svgr({ svgrOptions: {