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"; import { withSentryConfig } from "@sentry/nextjs"; // 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: process.env.NODE_ENV === "production" ? { optimizePackageImports: ["lucide-react", "framer-motion"], } : { // In development, enable webpack build worker for faster builds webpackBuildWorker: true, }, // Image optimization images: { formats: ["image/webp", "image/avif"], minimumCacheTTL: 60, remotePatterns: [ { protocol: "https", hostname: "i.scdn.co", }, { protocol: "https", hostname: "cdn.discordapp.com", }, { protocol: "https", hostname: "media.discordapp.net", }, ], }, // Webpack configuration webpack: (config, { dev, isServer }) => { // 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) { // Optimize module concatenation and chunking for the client build config.optimization = { ...config.optimization, moduleIds: "deterministic", chunkIds: "deterministic", splitChunks: { ...config.optimization?.splitChunks, maxSize: 200000, // keep chunks <200KB to avoid large-string serialization }, }; } } 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: https:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" : // Dev CSP: allow eval for tooling compatibility "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: https:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';"; 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, }, ], }, { source: "/api/(.*)", 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"); // Wrap with Sentry export default withSentryConfig( withBundleAnalyzer(withNextIntl(nextConfig)), { // For all available options, see: // https://github.com/getsentry/sentry-webpack-plugin#options org: "dk0", project: "portfolio", // Only print logs for uploading source maps in CI silent: !process.env.CI, // Upload a larger set of source maps for prettier stack traces (increases build time) widenClientFileUpload: true, // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. tunnelRoute: "/monitoring", // Webpack-specific options webpack: { // Automatically annotate React components to show their full name in breadcrumbs and session replay reactComponentAnnotation: { enabled: true, }, // Automatically tree-shake Sentry logger statements to reduce bundle size treeshake: { removeDebugLogging: true, }, // Enables automatic instrumentation of Vercel Cron Monitors automaticVercelMonitors: true, }, // Source maps configuration sourcemaps: { disable: false, }, } );