import type { NextConfig } from "next"; // Security headers. Inline styles/scripts are part of how Next hydrates and how the print windows are // built, so CSP allows 'unsafe-inline' for those while still pinning every origin to self. const csp = [ "default-src 'self'", // analytics.threadcount.tech is ThreadCount's own self-hosted Umami (homelab, via the tunnel). // Cloudflare Insights is not used and never was — the entries were permissive leftovers. "script-src 'self' 'unsafe-inline' https://challenges.cloudflare.com https://analytics.threadcount.tech", "frame-src https://challenges.cloudflare.com", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob:", "media-src 'self' blob:", "font-src 'self' data:", // errors.threadcount.tech is ThreadCount's own GlitchTip (homelab, via the tunnel). "connect-src 'self' https://analytics.threadcount.tech https://errors.threadcount.tech", "worker-src 'self' blob:", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", "object-src 'none'", ].join("; "); const headers = [ { key: "Content-Security-Policy", value: csp }, { 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=(self), microphone=(), geolocation=(), payment=(), usb=()" }, ]; const nextConfig: NextConfig = { poweredByHeader: false, // The Docker image (Community edition) runs Next's standalone server; the hosted box runs // `next start` from the full build, which standalone output would warn about. Opt in per build. ...(process.env.NEXT_OUTPUT === "standalone" ? { output: "standalone" as const } : {}), async headers() { return [{ source: "/(.*)", headers }]; }, }; export default nextConfig;