Files
portfolio/next.config.ts
2026-01-12 14:51:56 +00:00

155 lines
4.3 KiB
TypeScript

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
reactStrictMode: true,
// 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
// NOTE: `optimizePackageImports` can cause dev-time webpack runtime issues with some setups.
// Keep it enabled for production builds only.
experimental:
process.env.NODE_ENV === "production"
? {
optimizePackageImports: ["lucide-react", "framer-motion"],
}
: {},
// 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) => {
// Fix for module resolution issues
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
net: false,
tls: false,
};
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");
export default withBundleAnalyzer(withNextIntl(nextConfig));