import type { NextConfig } from "next"; const nextConfig: NextConfig = { // Verhindert den "X-Powered-By: Next.js"-Response-Header — verrimt keinen // funktionalen Vorteil, gibt Angreifern aber unnoetig Framework-Fingerprinting. poweredByHeader: false, // "standalone" buendelt eine minimale Node-Laufzeit inkl. Dependencies, // wodurch das Docker-Image (siehe Dockerfile) deutlich kleiner bleibt. output: "standalone", // Pakete, die zur Laufzeit eigene Dateien ueber einen __dirname-relativen // Pfad nachladen. Werden sie vom Bundler (Turbopack/Webpack) mitgebuendelt, // zeigt ihr __dirname auf einen virtuellen Bundle-Pfad ("/ROOT/...") statt // auf das echte Verzeichnis in node_modules — der Zugriff schlaegt dann mit // ENOENT fehl. "serverExternalPackages" laesst Next.js sie stattdessen ganz // normal per require() aus node_modules laden. // // - pdfkit: laedt seine Schriftart-Metadaten (.afm-Dateien). // - ffmpeg-static: exportiert den Pfad zur mitgelieferten ffmpeg-Binary // (siehe lib/video-transcode.ts). Gebuendelt lieferte es // "\ROOT\node_modules\...\ffmpeg.exe" -> jeder Video-Upload ohne // System-ffmpeg im PATH (also jede lokale Entwicklungsumgebung unter // Windows/macOS) scheiterte mit "Das Video konnte nicht verarbeitet // werden." serverExternalPackages: ["pdfkit", "ffmpeg-static"], images: { // AVIF vor WebP. Next.js liefert das erste Format, das der Browser laut // Accept-Header unterstuetzt; Browser ohne AVIF bekommen unveraendert // WebP, ganz alte das Original. // // Am Hero-Bild der Startseite nachgemessen (1920x1280), mit den // Einstellungen aus next/dist/server/image-optimizer.js — WebP q75 gegen // AVIF q47 (Next.js skaliert die Qualitaet mit 50/80) bei effort 3: // 640 px -43 %, 828 px -42 %, 1080 px -40 %, 1920 px -37 %. // // Die Kodierdauer spricht nicht dagegen: bei effort 3 lag AVIF in // derselben Messung gleichauf oder schneller als WebP (1920 px: 60 ms // gegen 122 ms). Die frueher hier vermutete teure Erstkodierung gibt es // in dieser Einstellung nicht. formats: ["image/avif", "image/webp"], // Ein Jahr statt der Next.js-Vorgabe von vier Wochen. Hochgeladene Bilder // sind unveraenderlich: lib/uploads.ts legt jede Datei unter einem eigenen // Namen ab, ein ausgetauschtes Produktbild bekommt also eine neue URL. // Ein kurzes TTL wuerde hier nur brauchbare Ergebnisse wegwerfen. minimumCacheTTL: 60 * 60 * 24 * 365, // Entspricht der Vorgabe von Next.js 16, die andere Werte bereits ablehnt // (gegen die laufende Seite geprueft: ?q=42 antwortet mit 400) — aendert // das Verhalten also nicht. Ausgeschrieben, damit eine spaetere // Erweiterung der Liste eine bewusste Entscheidung ist: jeder zusaetzlich // erlaubte Wert oeffnet einen eigenen Cache-Eintrag und eine eigene // Kodierung je Bild, von aussen ueber die URL ausloesbar. qualities: [75], // Ebenfalls die Vorgabe, ausdruecklich festgehalten: ein SVG ist ein // Dokument, das Skripte enthalten kann — same-origin ausgeliefert waere // das ein XSS-Vektor. Diese Zeile soll verhindern, dass der Schalter // spaeter beilaeufig umgelegt wird. dangerouslyAllowSVG: false, }, // Turbopacks Dateisystem-Caches (.next/dev/cache & .next/cache/turbopack) // kollidieren in diesem OneDrive-synchronisierten Arbeitsverzeichnis // wiederholt mit der Cloud-Synchronisierung ("Failed to open database" / // mmap-Timeout bei next dev, "Failed to compact database" beim // next-build-Shutdown). Beide Caches werden daher deaktiviert. experimental: { turbopackFileSystemCacheForDev: false, turbopackFileSystemCacheForBuild: false, }, // Das Favicon ist im Admin austauschbar und wird von app/icon.tsx erzeugt; // eine statische app/favicon.ico gibt es deshalb nicht mehr. Browser und // Crawler, die ohne direkt /favicon.ico abfragen, // bekommen dasselbe Bild. async rewrites() { return [{ source: "/favicon.ico", destination: "/icon" }]; }, async headers() { // Content-Security-Policy wird bewusst NICHT hier gesetzt: sie braucht // eine pro Request neue Nonce (siehe proxy.ts), was ein statischer // headers()-Eintrag hier nicht kann. return [ { source: "/(.*)", headers: [ { key: "X-Content-Type-Options", value: "nosniff" }, { key: "X-Frame-Options", value: "DENY" }, { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), payment=(self)" }, // Wirkt nur ueber HTTPS; im lokalen HTTP-Betrieb ignoriert der Browser den Header. { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" }, ], }, ]; }, }; export default nextConfig;