Files
portfolio/next.config.ts
denshooter 34a81a6437
All checks were successful
CI / CD / test-build (push) Successful in 10m10s
CI / CD / deploy-dev (push) Successful in 1m53s
CI / CD / deploy-production (push) Has been skipped
fix: resolve TypeScript errors in CI type-check
- next.config.ts: cssChunking 'loose' → false ('loose' not in type)
- ActivityFeed.test.tsx: remove always-truthy TS2872 literal expression

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:09 +01:00

221 lines
6.5 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
// 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
// <link rel="stylesheet"> 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));