i18n-Catalogues nicht im standalone-Bundle (P0)
14.05.2026
15 h 24 min
Behoben
Betroffene Services
- /chat
- /dashboard
- /vault
- /sprint
- /whiteboard
- alle (app)/* Routen
Summary
next-intl dynamic-imports wurden von Next.js `output: 'standalone'` nicht getraced. Plus die getRequestConfig-Wiring fehlte komplett. Alle eingeloggten User sahen einen friendly Error-Boundary statt der App. Health-Endpoint blieb grün, weil der das (app)-Layout bypassed.
Root Cause
Der i18n-Foundation-Commit shipte einen `getMessages()`-Helper, der die deutsche und englische JSON-Katalogdatei via `await import('../../messages/de.json')` dynamisch importierte. Next.js `output: 'standalone'` folgt beim File-Tracing ausschließlich statischen Import-Graphen — dynamische Imports auf relative JSON-Pfade landen deshalb nicht im Bundle. Zusätzlich fehlte der `createNextIntlPlugin`-Wrapper in `next.config.ts`, sodass `getRequestConfig` zur Laufzeit nie aufgerufen wurde. Beim ersten Request auf eine `(app)/*`-Route schmiss der Server-Render `MODULE_NOT_FOUND`, weshalb React Server Components den global-error-Boundary statt der App rieten. Der `/api/health`-Endpunkt blieb grün, weil sämtliche Health-Probes pure API-Routen sind und das `(app)/layout.tsx` nie laden.
Resolution
Hotfix #654 hat die JSON-Katalogdateien auf statische Imports umgestellt und gleichzeitig `outputFileTracingIncludes` als Belt-and-Suspenders für `messages/**/*.json` in `next.config.ts` ergänzt — damit lebt der Katalog garantiert im standalone-Bundle, selbst wenn ein späterer Refactor versehentlich wieder zu Dynamic-Imports zurückspringt. Direkt im Anschluss landete #656 mit dem fehlenden `createNextIntlPlugin`-Wrapper plus expliziter `i18n/request.ts`-Konfiguration, sodass `getRequestConfig` beim Boot tatsächlich gewired ist. Wir haben einen Regressions-Test in `src/i18n/__tests__/server.test.ts` ergänzt, der die Reference-Equality und die Fallback-Strategie pinned. Live-Verify lief um 17:02 UTC über Chrome MCP auf `/chat` und `/dashboard`; die finale Confirmation kam um 17:58 UTC nach dem Background-Reload aller eingeloggten Sessions.
Preventive Actions
Marketing-Routes plus eine `(app)/`-Sample-Route als Smoke-Test in den /api/health-Endpoint aufnehmen.
outputFileTracingIncludes als Belt-and-Suspenders für alle runtime-relevanten JSON- und YAML-Configs in `next.config.ts` pflegen.
Next.js standalone-Build im CI-Smoke-Test booten (`node .next/standalone/server.js`) und gegen `/chat` + `/dashboard` curl-en, bevor ein Merge in main erlaubt ist.
Migration zu next-intl in einen Phase-2-Sprint aufteilen statt in einem Single-PR — getrennte PRs für Plugin-Wrapper, Catalog-Migration und Locale-Switcher.
Sentry.captureException in `app/global-error.tsx` wiren, damit layout-level RSC-Throws pagen statt nur warning-level-loggen.
Timeline
| Zeit (UTC) | Ereignis |
|---|---|
| 01:46 | Bug-Commit (i18n-Foundation) wird nach main gemerged und auto-deployed. |
| 16:30 | Erster User-Report: `/chat` zeigt friendly Error-Boundary statt der App. |
| 16:38 | cc-samy reproduziert über Chrome MCP, isoliert Standalone-Tracing-Miss. |
| 16:45 | Hotfix-PR #654 (static-imports + outputFileTracingIncludes) gemerged. |
| 16:58 | Follow-up-PR #656 (createNextIntlPlugin-Wrapper) gemerged. |
| 17:02 | Live-Verify über Chrome MCP: `/chat` und `/dashboard` rendern wieder. |
| 17:58 | Finale Confirmation nach Background-Reload aller eingeloggten Sessions. |