import { config } from 'dotenv'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load .env from the project root (same dir as server.mjs) config({ path: join(__dirname, '.env') }); console.log('[server] Loaded .env from:', join(__dirname, '.env')); console.log('[server] SMTP_HOST:', process.env.SMTP_HOST || 'NOT SET'); console.log('[server] CONTACT_EMAIL_TO:', process.env.CONTACT_EMAIL_TO || 'NOT SET'); import { handler as ssrHandler } from './dist/server/entry.mjs'; import express from 'express'; import compression from 'compression'; const app = express(); // Server configuration const HOST = process.env.HOST || '0.0.0.0'; const PORT = process.env.PORT || 10000; // Gzip/Brotli compression - reduces transfer size by ~70% app.use(compression({ level: 6, // balanced speed/compression ratio threshold: 1024, // only compress responses > 1KB filter: (req, res) => { // Don't compress already-compressed formats if (req.headers['x-no-compression']) return false; return compression.filter(req, res); }, })); // MIME type map for explicit Content-Type headers const mimeTypes = { '.css': 'text/css', '.js': 'application/javascript', '.mjs': 'application/javascript', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.webp': 'image/webp', '.woff2': 'font/woff2', }; // Serve static assets with long-term caching // Hashed assets (_assets/) get 1-year immutable cache // Non-hashed assets get 1-hour cache with revalidation app.use('/_assets', express.static(join(__dirname, 'dist', 'client', '_assets'), { maxAge: '1y', immutable: true, etag: false, setHeaders: (res, filePath) => { const ext = filePath.slice(filePath.lastIndexOf('.')).toLowerCase(); if (mimeTypes[ext]) { res.setHeader('Content-Type', mimeTypes[ext]); } }, })); app.use(express.static(join(__dirname, 'dist', 'client'), { maxAge: '1h', setHeaders: (res, filePath) => { // Fonts: long-term cache since they're versioned by Google Fonts if (/\.(woff2?|ttf|otf|eot)$/.test(filePath)) { res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); } // Images: 1 week cache if (/\.(jpg|jpeg|png|gif|webp|avif|svg|ico)$/.test(filePath)) { res.setHeader('Cache-Control', 'public, max-age=604800, stale-while-revalidate=86400'); } }, })); // Use the Astro SSR handler for all routes app.use(ssrHandler); // Start the server app.listen(PORT, HOST, () => { console.log(`🚀 Server running at http://${HOST}:${PORT}`); console.log(`📁 Serving static files from: ${join(__dirname, 'dist', 'client')}`); console.log(`⚙️ Environment: ${process.env.NODE_ENV || 'development'}`); }); // Graceful shutdown process.on('SIGTERM', () => { console.log('SIGTERM received, shutting down gracefully...'); process.exit(0); }); process.on('SIGINT', () => { console.log('SIGINT received, shutting down gracefully...'); process.exit(0); });