Files
archivmail/features/PROJ-77-admin-tabs-dynamic-import.md
T
sysopsandClaude Sonnet 5 e59b11743a perf(PROJ-77): Admin-Tabs per next/dynamic laden statt statisch importieren
18 Admin-Tab-Komponenten wurden bisher alle statisch in src/app/admin/page.tsx
importiert, unabhängig davon welcher Tab tatsächlich geöffnet wird. Umstellung
auf next/dynamic mit gemeinsamem TabSkeleton-Loading-State reduziert das
initiale JS-Bundle der Admin-Route um 204,9 KB (-19,8%). Initial aktiver Tab
bleibt weiterhin serverseitig vorgerendert (ssr: true), kein Skeleton-Flash
beim ersten Laden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
2026-08-05 15:05:24 +02:00

3.0 KiB
Raw Blame History

id, title, status, created
id title status created
PROJ-77 Admin-Tab-Bundle-Optimierung (dynamic import statt 19 statische Imports) In Review 2026-08-05

Problem

src/app/admin/page.tsx (Zeile ~22-40) importiert alle 19 Admin-Tab- Komponenten statisch. Aktuell gibt es im gesamten Projekt 0 next/dynamic- Imports. Radix TabsContent unmountet zwar inaktive Tabs zur Laufzeit (kein gleichzeitiges Rendern), aber das initiale JS-Bundle enthält trotzdem den Code aller 19 Tabs, auch wenn ein Nutzer nur einen einzigen Tab je Sitzung öffnet.

Kein Crash-Bezug, reine Bundle-Größen-/Ladezeit-Optimierung.

Lösung (Vorschlag)

Jede Admin-Tab-Komponente auf next/dynamic mit Loading-State umstellen, z.B.:

const DashboardTab = dynamic(() => import('@/components/admin/tabs/DashboardTab'), {
  loading: () => <TabSkeleton />,
})

Umfang: 19 Komponenten, jeweils Import-Umstellung + einheitlicher Loading-Skeleton. Vor Umsetzung: kurzer Bundle-Size-Vergleich (next build Output) vorher/nachher, um den tatsächlichen Gewinn zu beziffern.

Implementation Notes

Umgesetzt am 2026-08-05.

Geänderte/neue Dateien

  • src/app/admin/page.tsx: 18 statische Tab-Imports (Zeile 2239) durch next/dynamic-Deklarationen ersetzt. Alle Tab-Komponenten sind Named Exports, daher jeweils dynamic(() => import("…/XyzTab").then((m) => m.XyzTab), { loading }). Eine gemeinsame const loading = () => <TabSkeleton /> wird für alle 18 Tabs wiederverwendet. ResetPasswordDialog/DeleteUserDialog (Global-Dialoge, immer sichtbar) bleiben bewusst statisch.
  • src/components/admin/TabSkeleton.tsx (neu): einheitlicher Lade-Platzhalter auf Basis der vorhandenen shadcn/ui-Skeleton- Komponente, mit aria-busy/aria-live und sr-only-Text.

Props-Typisierung: dynamic() inferiert die Props über den .then()-Rückgabewert korrekt; ein explizites ComponentType<Props> war nicht nötig. npm run build (inkl. TypeScript-Check) läuft fehlerfrei.

Bundle-Size-Vergleich (Next.js 16 / Turbopack zeigt keine Größen im Build-Output; gemessen als Summe der im prerenderten .next/server/app/admin.html referenzierten _next/static/*.js-Dateien, unkomprimiert):

Dateien Initiales JS
vorher 17 1035,6 KB
nachher 18 830,7 KB
Delta +1 204,9 KB (19,8 %)

Der Code der Tabs liegt jetzt in separaten Chunks, die erst beim Aktivieren des jeweiligen Tabs geladen werden.

Hinweis: dynamic() läuft hier mit Default-ssr: true, d.h. der initial aktive Tab wird weiterhin serverseitig vorgerendert — kein Flash-of-Skeleton beim ersten Laden des Dashboards.

Acceptance Criteria

  • Alle Admin-Tabs (18 Tab-Komponenten) per next/dynamic geladen, mit Loading-Skeleton.
  • npm run build Bundle-Size-Report zeigt messbare Reduktion der initialen Admin-Route-Chunk-Größe (204,9 KB / 19,8 %).
  • Funktionale Regression: alle Tabs weiterhin normal nutzbar (manueller Klick-Test durch alle Tabs) — noch zu verifizieren, kann nicht automatisiert erfolgen; offen für QA.