import createNextIntlPlugin from "next-intl/plugin"; const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts"); /** * Hosts the Next.js image optimizer is allowed to fetch from. Without these, every * remote (MinIO uploads/avatars/template art) returns HTTP 400. Derived from * NEXT_PUBLIC_MINIO_URL (baked at build) plus the dev MinIO hosts. */ function imageRemotePatterns() { const patterns = []; const seen = new Set(); const add = (protocol, hostname, port) => { if (!hostname) return; const key = `${protocol}//${hostname}:${port}`; if (seen.has(key)) return; seen.add(key); patterns.push({ protocol, hostname, port: port || "", pathname: "/**" }); }; const url = process.env.NEXT_PUBLIC_MINIO_URL; if (url) { try { const u = new URL(url); add(u.protocol.replace(":", ""), u.hostname, u.port); } catch { /* ignore malformed env */ } } // dev / docker fallbacks add("http", "172.28.144.1", "9000"); add("http", "localhost", "9000"); add("http", "minio", "9000"); return patterns; } /** @type {import('next').NextConfig} */ const nextConfig = { output: "standalone", webpack: (config, { isServer, webpack }) => { if (!isServer) { config.output.globalObject = "self"; } // react-konva / konva must not load the Node `canvas` package in the browser bundle config.resolve.alias = { ...config.resolve.alias, canvas: false, }; config.plugins.push( new webpack.IgnorePlugin({ resourceRegExp: /^canvas$/, }) ); return config; }, images: { // Allow the image optimizer to fetch MinIO uploads (avatars, template art, …). remotePatterns: imageRemotePatterns(), // Placeholder art is now a same-origin SVG from /api/placeholder (offline-safe). // dangerouslyAllowSVG only ever serves our own generated gradients — never user // uploads — and the CSP + attachment disposition neutralise any script content. dangerouslyAllowSVG: true, contentDispositionType: "attachment", contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", }, // Required for ffmpeg.wasm (SharedArrayBuffer needs COOP + COEP headers) async headers() { return [ { source: "/(.*)", headers: [ { key: "Cross-Origin-Opener-Policy", value: "same-origin" }, { key: "Cross-Origin-Embedder-Policy", value: "require-corp" }, ], }, ]; }, }; export default withNextIntl(nextConfig);