/** @type {import('next').NextConfig} */ const securityHeaders = [ { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' }, { key: 'X-Content-Type-Options', value: 'nosniff' }, { key: 'X-Frame-Options', value: 'DENY' }, { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }, { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' }, { key: 'Content-Security-Policy', value: [ "default-src 'self'", "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob: https:", "font-src 'self' data:", "connect-src 'self' https: wss:", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", "object-src 'none'", ].join('; '), }, ] const apiOrigin = (process.env.API_INTERNAL_URL ?? process.env.API_URL ?? 'http://localhost:4000').replace(/\/api\/v1\/?$/, '') const apiUrl = new URL(apiOrigin) const ADMIN_BASE_PATH = '/admin' function ensureAdminAssetPrefix(rawValue) { if (!rawValue) return undefined try { const url = new URL(rawValue) const pathname = url.pathname.replace(/\/$/, '') if (!pathname.endsWith(ADMIN_BASE_PATH)) { url.pathname = `${pathname}${ADMIN_BASE_PATH}` } return url.toString().replace(/\/$/, '') } catch { const trimmed = rawValue.replace(/\/$/, '') return trimmed.endsWith(ADMIN_BASE_PATH) ? trimmed : `${trimmed}${ADMIN_BASE_PATH}` } } // In Docker dev the admin app runs on port 3002 while the marketplace proxy // serves it from port 3000. When ADMIN_ASSET_PREFIX is set, Next emits // absolute chunk URLs so /admin pages load their JS/CSS and HMR directly from // port 3002, bypassing the proxy (which can't upgrade WebSocket connections). const assetPrefix = ensureAdminAssetPrefix(process.env.ADMIN_ASSET_PREFIX) const nextConfig = { basePath: ADMIN_BASE_PATH, ...(assetPrefix ? { assetPrefix } : {}), images: { remotePatterns: [ { protocol: 'https', hostname: 'res.cloudinary.com', }, { protocol: apiUrl.protocol.replace(':', ''), hostname: apiUrl.hostname, ...(apiUrl.port ? { port: apiUrl.port } : {}), pathname: '/storage/**', }, ], }, transpilePackages: ['@rentaldrivego/types'], async headers() { return [ { source: '/:path*', headers: securityHeaders, }, ] }, async rewrites() { return [ { source: '/api/:path*', destination: `${apiOrigin}/api/:path*`, }, ] }, } module.exports = nextConfig