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:
@@ -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,6 +378,7 @@ 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">
|
||||||
|
<Suspense fallback={<LoadingFallback />}>
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* Main Dashboard (Search) */}
|
{/* Main Dashboard (Search) */}
|
||||||
<Route path="/" element={<ProtectedRoute><SearchDashboard /></ProtectedRoute>} />
|
<Route path="/" element={<ProtectedRoute><SearchDashboard /></ProtectedRoute>} />
|
||||||
@@ -409,6 +434,7 @@ function AppContent() {
|
|||||||
<Route path="/teams" element={<ProtectedRoute requirePermission="view_reports"><TeamDashboard /></ProtectedRoute>} />
|
<Route path="/teams" element={<ProtectedRoute requirePermission="view_reports"><TeamDashboard /></ProtectedRoute>} />
|
||||||
<Route path="/configuration" element={<Navigate to="/settings/fte-config" replace />} />
|
<Route path="/configuration" element={<Navigate to="/settings/fte-config" replace />} />
|
||||||
</Routes>
|
</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 (
|
||||||
|
<Suspense fallback={<LoadingFallback />}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||||
<Route path="/reset-password" element={<ResetPassword />} />
|
<Route path="/reset-password" element={<ResetPassword />} />
|
||||||
<Route path="/accept-invitation" element={<AcceptInvitation />} />
|
<Route path="/accept-invitation" element={<AcceptInvitation />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
Reference in New Issue
Block a user