import { readFileSync, existsSync } from 'node:fs' import path from 'node:path' import { gzipSync } from 'node:zlib' /** * Bündel-Budget aus docs/09-performance-und-ux.md: die erste Route muss unter * 120 kB JavaScript nach Kompression bleiben. * * Gemessen wird je Route die Vereinigung ihrer JavaScript-Chunks aus * app-build-manifest.json, gzip-komprimiert. Das entspricht dem, was Next * selbst als "First Load JS" ausweist. Cloudflare liefert Brotli aus, das ist * noch etwas kleiner, gzip ist damit die vorsichtigere Schätzung. */ const BUDGET_BYTES = 120 * 1024 const appDir = process.cwd() // Dasselbe Verzeichnis wie next.config.ts, damit auch ein Build neben einem // laufenden Entwicklungsserver gemessen werden kann. const distDir = process.env.NEXT_DIST_DIR ?? '.next' const manifestPath = path.join(appDir, distDir, 'app-build-manifest.json') if (!existsSync(manifestPath)) { console.error('Kein Build gefunden. Zuerst `pnpm --filter @afis/web run build` laufen lassen.') process.exit(1) } const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) const routes = Object.entries(manifest.pages ?? {}) if (routes.length === 0) { console.error('Keine Routen im Manifest gefunden.') process.exit(1) } const measured = routes .map(([route, files]) => { let bytes = 0 for (const file of new Set(files)) { if (!file.endsWith('.js')) continue const full = path.join(appDir, distDir, file) if (!existsSync(full)) continue bytes += gzipSync(readFileSync(full)).byteLength } return { route, bytes } }) .sort((a, b) => b.bytes - a.bytes) const kb = (n) => `${(n / 1024).toFixed(1)} kB` for (const { route, bytes } of measured) { console.log(`${bytes > BUDGET_BYTES ? '✗' : '✓'} ${route.padEnd(28)} ${kb(bytes)}`) } const worst = measured[0] console.log(`\nGrößte Route: ${worst.route} mit ${kb(worst.bytes)}, Budget ${kb(BUDGET_BYTES)}`) if (worst.bytes > BUDGET_BYTES) { console.error(`Budget überschritten um ${kb(worst.bytes - BUDGET_BYTES)}.`) process.exit(1) }