import type { NextConfig } from "next"; import dotenv from "dotenv"; import path from "path"; import bundleAnalyzer from "@next/bundle-analyzer"; import createNextIntlPlugin from "next-intl/plugin"; // Load the .env file from the working directory dotenv.config({ path: path.resolve(process.cwd(), ".env") }); const nextConfig: NextConfig = { // Enable standalone output for Docker output: "standalone", outputFileTracingRoot: path.join(process.cwd()), // Optimize for production compress: true, poweredByHeader: false, // React Strict Mode // In dev, React StrictMode double-mount can cause visible animation flicker // (Framer Motion "fade starts, disappears, then pops"). reactStrictMode: process.env.NODE_ENV === "production", // Disable ESLint during build for Docker eslint: { ignoreDuringBuilds: process.env.NODE_ENV === "production", }, // Environment variables env: { NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL, }, // Performance optimizations experimental: { // Tree-shake barrel-file packages in both dev and production optimizePackageImports: ["lucide-react", "framer-motion", "react-icons", "@tiptap/react"], // Merge all CSS into a single chunk to eliminate the render-blocking CSS chain // (84dc7384.css → 3aefc04b.css sequential dependency reported by PageSpeed). cssChunking: false, // Note: optimizeCss (critters) is intentionally disabled — it converts the main // to a JS-deferred preload, which PageSpeed reads as a // sequential CSS chain and reports 410ms of render-blocking. ...(process.env.NODE_ENV !== "production" ? { webpackBuildWorker: true } : {}), }, // Image optimization images: { formats: ["image/webp", "image/avif"], minimumCacheTTL: 2592000, remotePatterns: [ { protocol: "https", hostname: "i.scdn.co", }, { protocol: "https", hostname: "cdn.discordapp.com", }, { protocol: "https", hostname: "media.discordapp.net", }, { protocol: "https", hostname: "cms.dk0.dev", }, { protocol: "https", hostname: "assets.hardcover.app", }, { protocol: "https", hostname: "dki.one", }, { protocol: "https", hostname: "images.unsplash.com", }, ], }, // Webpack configuration webpack: (config, { dev, isServer, webpack }) => { // Fix for module resolution issues config.resolve.fallback = { ...config.resolve.fallback, fs: false, net: false, tls: false, }; // Optimize webpack cache - fix "Serializing big strings" warnings in dev by avoiding FS cache if (dev) { config.cache = { type: "memory", maxGenerations: 5, }; if (!isServer) { // Suppress framer-motion source map errors in development config.plugins.push( new webpack.SourceMapDevToolPlugin({ filename: "[file].map", exclude: [/framer-motion/, /LayoutGroupContext/], }) ); } } return config; }, // Security and cache headers async headers() { const csp = process.env.NODE_ENV === "production" ? // Avoid `unsafe-eval` in production (reduces XSS impact and enables stronger CSP) "default-src 'self'; script-src 'self' 'unsafe-inline'; script-src-elem 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob: https:; connect-src 'self' https://*.dk0.dev; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; worker-src 'self' blob:;" : // Dev CSP: allow eval for tooling compatibility, and localhost for HMR/API "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src-elem 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob: https: http://localhost:3000; connect-src 'self' http://localhost:3000 ws://localhost:3000 https://*.dk0.dev; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; worker-src 'self' blob:;"; return [ { source: "/(.*)", headers: [ { key: "X-DNS-Prefetch-Control", value: "on", }, { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload", }, { key: "X-Frame-Options", value: "DENY", }, { key: "X-Content-Type-Options", value: "nosniff", }, { key: "X-XSS-Protection", value: "1; mode=block", }, { key: "Referrer-Policy", value: "strict-origin-when-cross-origin", }, { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()", }, { key: "Content-Security-Policy", value: csp, }, ], }, { // Allow bfcache for n8n routes: use no-cache (revalidate) instead of no-store source: "/api/n8n/(.*)", headers: [ { key: "Cache-Control", value: "no-cache, must-revalidate", }, ], }, { source: "/api/auth/(.*)", headers: [ { key: "Cache-Control", value: "no-store, no-cache, must-revalidate, proxy-revalidate", }, ], }, { source: "/api/email/(.*)", headers: [ { key: "Cache-Control", value: "no-store, no-cache, must-revalidate, proxy-revalidate", }, ], }, { source: "/api/contacts/(.*)", headers: [ { key: "Cache-Control", value: "no-store, no-cache, must-revalidate, proxy-revalidate", }, ], }, { source: "/_next/static/(.*)", headers: [ { key: "Cache-Control", // In dev, aggressive caching breaks HMR and can brick a tab with stale chunks. value: process.env.NODE_ENV === "production" ? "public, max-age=31536000, immutable" : "no-store", }, ], }, ]; }, }; const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === "true", }); const withNextIntl = createNextIntlPlugin("./i18n/request.ts"); export default withBundleAnalyzer(withNextIntl(nextConfig));