/** @type {import('next').NextConfig} */ const nextConfig = { output: "standalone", // No `images.remotePatterns` and no rewrite to MinIO: images are served by the // session-gated route at src/app/minio-images/[...key]/route.ts. The rewrite this // replaced was an unauthenticated proxy onto the bucket. Nothing uses next/image, // and it must stay that way - the optimizer fetches without the session cookie. async headers() { return [ { source: "/(.*)", headers: [ { key: "X-Frame-Options", value: "DENY", }, { key: "X-Content-Type-Options", value: "nosniff", }, { key: "Referrer-Policy", value: "strict-origin-when-cross-origin", }, { key: "X-DNS-Prefetch-Control", value: "on", }, { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload", }, { key: "Permissions-Policy", value: "camera=(self), microphone=(), geolocation=(), interest-cohort=()", }, { key: "Content-Security-Policy", value: [ "default-src 'self'", "script-src 'self' 'unsafe-inline' 'unsafe-eval'", "style-src 'self' 'unsafe-inline'", // Product and drink images are mirrored into our own storage and served // from 'self' via /minio-images, so third-party image hosts do not belong // here. The exceptions are OAuth avatars, which the provider hosts and we // only ever receive as a URL at sign-in. [ "img-src 'self' data: blob:", "https://lh3.googleusercontent.com", "https://avatars.githubusercontent.com", ].join(" "), "font-src 'self'", "connect-src 'self'", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", ].join("; "), }, ], }, ]; }, }; export default nextConfig;