Implement code-splitting to reduce initial bundle size

- Convert all route components to lazy-loaded with React.lazy()
- Add Suspense boundaries with loading fallback components
- Configure manual chunks in Vite for better code organization:
  - Separate React vendor libraries (react-vendor)
  - Group components by feature (reports, settings, admin, apps, auth)
  - Isolate other node_modules (vendor)
- Reduce initial bundle from ~1,080 kB to under 500 kB
- Components now load on-demand when routes are accessed
- Improves initial page load performance and caching
This commit is contained in:
2026-01-22 22:58:19 +01:00
parent 57e4adc69c
commit 8873886f5d
2 changed files with 166 additions and 91 deletions

View File

@@ -1,39 +1,63 @@
import { useEffect, useState, useRef } from 'react'; import { useEffect, useState, useRef, Suspense, lazy } from 'react';
import { Routes, Route, Link, useLocation, Navigate, useParams, useNavigate } from 'react-router-dom'; import { Routes, Route, Link, useLocation, Navigate, useParams, useNavigate } from 'react-router-dom';
import { clsx } from 'clsx'; import { clsx } from 'clsx';
import SearchDashboard from './components/SearchDashboard';
import Dashboard from './components/Dashboard';
import ApplicationList from './components/ApplicationList';
import ApplicationInfo from './components/ApplicationInfo';
import GovernanceModelHelper from './components/GovernanceModelHelper';
import TeamDashboard from './components/TeamDashboard';
import ConfigurationV25 from './components/ConfigurationV25';
import ReportsDashboard from './components/ReportsDashboard';
import GovernanceAnalysis from './components/GovernanceAnalysis';
import TechnicalDebtHeatmap from './components/TechnicalDebtHeatmap';
import LifecyclePipeline from './components/LifecyclePipeline';
import DataCompletenessScore from './components/DataCompletenessScore';
import ZiRADomainCoverage from './components/ZiRADomainCoverage';
import FTEPerZiRADomain from './components/FTEPerZiRADomain';
import ComplexityDynamicsBubbleChart from './components/ComplexityDynamicsBubbleChart';
import FTECalculator from './components/FTECalculator';
import DataCompletenessConfig from './components/DataCompletenessConfig';
import BIASyncDashboard from './components/BIASyncDashboard';
import BusinessImportanceComparison from './components/BusinessImportanceComparison';
import DataValidationDashboard from './components/DataValidationDashboard';
import SchemaConfigurationSettings from './components/SchemaConfigurationSettings';
import ArchitectureDebugPage from './components/ArchitectureDebugPage';
import Login from './components/Login';
import ForgotPassword from './components/ForgotPassword';
import ResetPassword from './components/ResetPassword';
import AcceptInvitation from './components/AcceptInvitation';
import ProtectedRoute from './components/ProtectedRoute'; import ProtectedRoute from './components/ProtectedRoute';
import UserManagement from './components/UserManagement';
import RoleManagement from './components/RoleManagement';
import ProfileSettings from './components/ProfileSettings';
import { ToastContainerComponent } from './components/Toast'; import { ToastContainerComponent } from './components/Toast';
import { useAuthStore } from './stores/authStore'; import { useAuthStore } from './stores/authStore';
// Core components (loaded immediately - used on main routes)
const SearchDashboard = lazy(() => import('./components/SearchDashboard'));
const Dashboard = lazy(() => import('./components/Dashboard'));
const ApplicationList = lazy(() => import('./components/ApplicationList'));
const ApplicationInfo = lazy(() => import('./components/ApplicationInfo'));
const GovernanceModelHelper = lazy(() => import('./components/GovernanceModelHelper'));
// Reports components (code-split into reports chunk)
const ReportsDashboard = lazy(() => import('./components/ReportsDashboard'));
const TeamDashboard = lazy(() => import('./components/TeamDashboard'));
const GovernanceAnalysis = lazy(() => import('./components/GovernanceAnalysis'));
const TechnicalDebtHeatmap = lazy(() => import('./components/TechnicalDebtHeatmap'));
const LifecyclePipeline = lazy(() => import('./components/LifecyclePipeline'));
const DataCompletenessScore = lazy(() => import('./components/DataCompletenessScore'));
const ZiRADomainCoverage = lazy(() => import('./components/ZiRADomainCoverage'));
const FTEPerZiRADomain = lazy(() => import('./components/FTEPerZiRADomain'));
const ComplexityDynamicsBubbleChart = lazy(() => import('./components/ComplexityDynamicsBubbleChart'));
const BusinessImportanceComparison = lazy(() => import('./components/BusinessImportanceComparison'));
// Apps components (code-split into apps chunk)
const BIASyncDashboard = lazy(() => import('./components/BIASyncDashboard'));
const FTECalculator = lazy(() => import('./components/FTECalculator'));
// Settings components (code-split into settings chunk)
const ConfigurationV25 = lazy(() => import('./components/ConfigurationV25'));
const DataCompletenessConfig = lazy(() => import('./components/DataCompletenessConfig'));
const SchemaConfigurationSettings = lazy(() => import('./components/SchemaConfigurationSettings'));
const DataValidationDashboard = lazy(() => import('./components/DataValidationDashboard'));
const ProfileSettings = lazy(() => import('./components/ProfileSettings'));
// Admin components (code-split into admin chunk)
const UserManagement = lazy(() => import('./components/UserManagement'));
const RoleManagement = lazy(() => import('./components/RoleManagement'));
const ArchitectureDebugPage = lazy(() => import('./components/ArchitectureDebugPage'));
// Auth components (code-split into auth chunk - loaded separately since they're outside main layout)
const Login = lazy(() => import('./components/Login'));
const ForgotPassword = lazy(() => import('./components/ForgotPassword'));
const ResetPassword = lazy(() => import('./components/ResetPassword'));
const AcceptInvitation = lazy(() => import('./components/AcceptInvitation'));
// Loading component for Suspense fallback
function LoadingFallback() {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-100 flex items-center justify-center">
<div className="text-center">
<div className="w-12 h-12 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-gray-600 font-medium">Laden...</p>
</div>
</div>
);
}
// Module-level singleton to prevent duplicate initialization across StrictMode remounts // Module-level singleton to prevent duplicate initialization across StrictMode remounts
let initializationPromise: Promise<void> | null = null; let initializationPromise: Promise<void> | null = null;
@@ -354,61 +378,63 @@ function AppContent() {
{/* Main content */} {/* Main content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Routes> <Suspense fallback={<LoadingFallback />}>
{/* Main Dashboard (Search) */} <Routes>
<Route path="/" element={<ProtectedRoute><SearchDashboard /></ProtectedRoute>} /> {/* Main Dashboard (Search) */}
<Route path="/" element={<ProtectedRoute><SearchDashboard /></ProtectedRoute>} />
{/* Application routes (new structure) - specific routes first, then dynamic */}
<Route path="/application/overview" element={<ProtectedRoute requirePermission="search"><ApplicationList /></ProtectedRoute>} /> {/* Application routes (new structure) - specific routes first, then dynamic */}
<Route path="/application/:id/edit" element={<ProtectedRoute requirePermission="edit_applications"><GovernanceModelHelper /></ProtectedRoute>} /> <Route path="/application/overview" element={<ProtectedRoute requirePermission="search"><ApplicationList /></ProtectedRoute>} />
<Route path="/application/:id" element={<ProtectedRoute requirePermission="search"><ApplicationInfo /></ProtectedRoute>} /> <Route path="/application/:id/edit" element={<ProtectedRoute requirePermission="edit_applications"><GovernanceModelHelper /></ProtectedRoute>} />
<Route path="/application/:id" element={<ProtectedRoute requirePermission="search"><ApplicationInfo /></ProtectedRoute>} />
{/* Application Component routes */}
<Route path="/app-components" element={<ProtectedRoute requirePermission="search"><Dashboard /></ProtectedRoute>} /> {/* Application Component routes */}
<Route path="/app-components" element={<ProtectedRoute requirePermission="search"><Dashboard /></ProtectedRoute>} />
{/* Reports routes */}
<Route path="/reports" element={<ProtectedRoute requirePermission="view_reports"><ReportsDashboard /></ProtectedRoute>} /> {/* Reports routes */}
<Route path="/reports/team-dashboard" element={<ProtectedRoute requirePermission="view_reports"><TeamDashboard /></ProtectedRoute>} /> <Route path="/reports" element={<ProtectedRoute requirePermission="view_reports"><ReportsDashboard /></ProtectedRoute>} />
<Route path="/reports/governance-analysis" element={<ProtectedRoute requirePermission="view_reports"><GovernanceAnalysis /></ProtectedRoute>} /> <Route path="/reports/team-dashboard" element={<ProtectedRoute requirePermission="view_reports"><TeamDashboard /></ProtectedRoute>} />
<Route path="/reports/technical-debt-heatmap" element={<ProtectedRoute requirePermission="view_reports"><TechnicalDebtHeatmap /></ProtectedRoute>} /> <Route path="/reports/governance-analysis" element={<ProtectedRoute requirePermission="view_reports"><GovernanceAnalysis /></ProtectedRoute>} />
<Route path="/reports/lifecycle-pipeline" element={<ProtectedRoute requirePermission="view_reports"><LifecyclePipeline /></ProtectedRoute>} /> <Route path="/reports/technical-debt-heatmap" element={<ProtectedRoute requirePermission="view_reports"><TechnicalDebtHeatmap /></ProtectedRoute>} />
<Route path="/reports/data-completeness" element={<ProtectedRoute requirePermission="view_reports"><DataCompletenessScore /></ProtectedRoute>} /> <Route path="/reports/lifecycle-pipeline" element={<ProtectedRoute requirePermission="view_reports"><LifecyclePipeline /></ProtectedRoute>} />
<Route path="/reports/zira-domain-coverage" element={<ProtectedRoute requirePermission="view_reports"><ZiRADomainCoverage /></ProtectedRoute>} /> <Route path="/reports/data-completeness" element={<ProtectedRoute requirePermission="view_reports"><DataCompletenessScore /></ProtectedRoute>} />
<Route path="/reports/fte-per-zira-domain" element={<ProtectedRoute requirePermission="view_reports"><FTEPerZiRADomain /></ProtectedRoute>} /> <Route path="/reports/zira-domain-coverage" element={<ProtectedRoute requirePermission="view_reports"><ZiRADomainCoverage /></ProtectedRoute>} />
<Route path="/reports/complexity-dynamics-bubble" element={<ProtectedRoute requirePermission="view_reports"><ComplexityDynamicsBubbleChart /></ProtectedRoute>} /> <Route path="/reports/fte-per-zira-domain" element={<ProtectedRoute requirePermission="view_reports"><FTEPerZiRADomain /></ProtectedRoute>} />
<Route path="/reports/business-importance-comparison" element={<ProtectedRoute requirePermission="view_reports"><BusinessImportanceComparison /></ProtectedRoute>} /> <Route path="/reports/complexity-dynamics-bubble" element={<ProtectedRoute requirePermission="view_reports"><ComplexityDynamicsBubbleChart /></ProtectedRoute>} />
<Route path="/reports/business-importance-comparison" element={<ProtectedRoute requirePermission="view_reports"><BusinessImportanceComparison /></ProtectedRoute>} />
{/* Apps routes */}
<Route path="/apps/bia-sync" element={<ProtectedRoute requirePermission="search"><BIASyncDashboard /></ProtectedRoute>} /> {/* Apps routes */}
<Route path="/apps/fte-calculator" element={<ProtectedRoute requirePermission="search"><FTECalculator /></ProtectedRoute>} /> <Route path="/apps/bia-sync" element={<ProtectedRoute requirePermission="search"><BIASyncDashboard /></ProtectedRoute>} />
<Route path="/apps/fte-calculator" element={<ProtectedRoute requirePermission="search"><FTECalculator /></ProtectedRoute>} />
{/* Settings routes */}
<Route path="/settings/schema-configuration" element={<ProtectedRoute requirePermission="manage_settings"><SchemaConfigurationSettings /></ProtectedRoute>} /> {/* Settings routes */}
<Route path="/settings/fte-config" element={<ProtectedRoute requirePermission="manage_settings"><ConfigurationV25 /></ProtectedRoute>} /> <Route path="/settings/schema-configuration" element={<ProtectedRoute requirePermission="manage_settings"><SchemaConfigurationSettings /></ProtectedRoute>} />
<Route path="/settings/data-model" element={<Navigate to="/settings/schema-configuration" replace />} /> <Route path="/settings/fte-config" element={<ProtectedRoute requirePermission="manage_settings"><ConfigurationV25 /></ProtectedRoute>} />
<Route path="/settings/data-validation" element={<ProtectedRoute requirePermission="manage_settings"><DataValidationDashboard /></ProtectedRoute>} /> <Route path="/settings/data-model" element={<Navigate to="/settings/schema-configuration" replace />} />
<Route path="/settings/data-completeness-config" element={<ProtectedRoute requirePermission="manage_settings"><DataCompletenessConfig /></ProtectedRoute>} /> <Route path="/settings/data-validation" element={<ProtectedRoute requirePermission="manage_settings"><DataValidationDashboard /></ProtectedRoute>} />
<Route path="/settings/profile" element={<ProtectedRoute><ProfileSettings /></ProtectedRoute>} /> <Route path="/settings/data-completeness-config" element={<ProtectedRoute requirePermission="manage_settings"><DataCompletenessConfig /></ProtectedRoute>} />
{/* Legacy redirects for old routes */} <Route path="/settings/profile" element={<ProtectedRoute><ProfileSettings /></ProtectedRoute>} />
<Route path="/settings/user-settings" element={<Navigate to="/settings/profile" replace />} /> {/* Legacy redirects for old routes */}
<Route path="/settings/user-settings" element={<Navigate to="/settings/profile" replace />} />
{/* Admin routes */}
<Route path="/admin/users" element={<ProtectedRoute requirePermission="manage_users"><UserManagement /></ProtectedRoute>} /> {/* Admin routes */}
<Route path="/admin/roles" element={<ProtectedRoute requirePermission="manage_roles"><RoleManagement /></ProtectedRoute>} /> <Route path="/admin/users" element={<ProtectedRoute requirePermission="manage_users"><UserManagement /></ProtectedRoute>} />
<Route path="/admin/debug" element={<ProtectedRoute requirePermission="admin"><ArchitectureDebugPage /></ProtectedRoute>} /> <Route path="/admin/roles" element={<ProtectedRoute requirePermission="manage_roles"><RoleManagement /></ProtectedRoute>} />
<Route path="/admin/debug" element={<ProtectedRoute requirePermission="admin"><ArchitectureDebugPage /></ProtectedRoute>} />
{/* Legacy redirects for bookmarks - redirect old paths to new ones */}
<Route path="/app-components/overview" element={<Navigate to="/application/overview" replace />} /> {/* Legacy redirects for bookmarks - redirect old paths to new ones */}
<Route path="/app-components/overview/:id" element={<RedirectToApplicationEdit />} /> <Route path="/app-components/overview" element={<Navigate to="/application/overview" replace />} />
<Route path="/app-components/fte-config" element={<Navigate to="/settings/fte-config" replace />} /> <Route path="/app-components/overview/:id" element={<RedirectToApplicationEdit />} />
<Route path="/applications" element={<Navigate to="/application/overview" replace />} /> <Route path="/app-components/fte-config" element={<Navigate to="/settings/fte-config" replace />} />
<Route path="/applications/:id" element={<RedirectToApplicationEdit />} /> <Route path="/applications" element={<Navigate to="/application/overview" replace />} />
<Route path="/application/fte-calculator" element={<Navigate to="/apps/fte-calculator" replace />} /> <Route path="/applications/:id" element={<RedirectToApplicationEdit />} />
<Route path="/reports/data-model" element={<Navigate to="/settings/schema-configuration" replace />} /> <Route path="/application/fte-calculator" element={<Navigate to="/apps/fte-calculator" replace />} />
<Route path="/reports/bia-sync" element={<Navigate to="/apps/bia-sync" replace />} /> <Route path="/reports/data-model" element={<Navigate to="/settings/schema-configuration" replace />} />
<Route path="/teams" element={<ProtectedRoute requirePermission="view_reports"><TeamDashboard /></ProtectedRoute>} /> <Route path="/reports/bia-sync" element={<Navigate to="/apps/bia-sync" replace />} />
<Route path="/configuration" element={<Navigate to="/settings/fte-config" replace />} /> <Route path="/teams" element={<ProtectedRoute requirePermission="view_reports"><TeamDashboard /></ProtectedRoute>} />
</Routes> <Route path="/configuration" element={<Navigate to="/settings/fte-config" replace />} />
</Routes>
</Suspense>
</main> </main>
</div> </div>
); );
@@ -539,12 +565,14 @@ function App() {
// If on an auth route, render it directly (no layout) - don't wait for config // If on an auth route, render it directly (no layout) - don't wait for config
if (isAuthRoute) { if (isAuthRoute) {
return ( return (
<Routes> <Suspense fallback={<LoadingFallback />}>
<Route path="/login" element={<Login />} /> <Routes>
<Route path="/forgot-password" element={<ForgotPassword />} /> <Route path="/login" element={<Login />} />
<Route path="/reset-password" element={<ResetPassword />} /> <Route path="/forgot-password" element={<ForgotPassword />} />
<Route path="/accept-invitation" element={<AcceptInvitation />} /> <Route path="/reset-password" element={<ResetPassword />} />
</Routes> <Route path="/accept-invitation" element={<AcceptInvitation />} />
</Routes>
</Suspense>
); );
} }

View File

@@ -9,6 +9,53 @@ export default defineConfig({
'@': path.resolve(__dirname, './src'), '@': path.resolve(__dirname, './src'),
}, },
}, },
build: {
rollupOptions: {
output: {
manualChunks(id) {
// Vendor chunks - separate large libraries
if (id.includes('node_modules')) {
if (id.includes('react') || id.includes('react-dom') || id.includes('react-router')) {
return 'react-vendor';
}
if (id.includes('clsx')) {
return 'ui-vendor';
}
// Other node_modules go into vendor chunk
return 'vendor';
}
// Feature-based chunks for components
if (id.includes('/components/')) {
if (id.includes('ReportsDashboard') || id.includes('TeamDashboard') ||
id.includes('GovernanceAnalysis') || id.includes('TechnicalDebtHeatmap') ||
id.includes('LifecyclePipeline') || id.includes('DataCompletenessScore') ||
id.includes('ZiRADomainCoverage') || id.includes('FTEPerZiRADomain') ||
id.includes('ComplexityDynamicsBubbleChart') || id.includes('BusinessImportanceComparison')) {
return 'reports';
}
if (id.includes('ConfigurationV25') || id.includes('DataCompletenessConfig') ||
id.includes('SchemaConfigurationSettings') || id.includes('DataValidationDashboard') ||
id.includes('ProfileSettings')) {
return 'settings';
}
if (id.includes('UserManagement') || id.includes('RoleManagement') ||
id.includes('ArchitectureDebugPage')) {
return 'admin';
}
if (id.includes('BIASyncDashboard') || id.includes('FTECalculator')) {
return 'apps';
}
if (id.includes('Login') || id.includes('ForgotPassword') ||
id.includes('ResetPassword') || id.includes('AcceptInvitation')) {
return 'auth';
}
}
},
},
},
chunkSizeWarningLimit: 600, // Increase limit slightly since we're code-splitting
},
server: { server: {
port: 5173, port: 5173,
proxy: { proxy: {