The frontend is a React 19 application built with Vite and TypeScript. It lacks code splitting and lazy loading, resulting in a large initial bundle. The application has 15 pages and 6 components with two heavy chart-based components using Recharts.
- Dashboard.tsx - Main dashboard with metrics and CashFlowChart
- Forecast.tsx - Cash flow forecasting with ForecastChart (Monte Carlo)
- DecisionEngine.tsx - Report generation and payment execution
- Transactions.tsx - Transaction management with CSV import
- Invoices.tsx - Invoice registry with vendor table
- ActionComposer.tsx - Email/WhatsApp messaging compose
- VendorDetail.tsx - Individual vendor detail view
- Settings.tsx - System configuration and API integrations
- Preferences.tsx - User preferences and notification settings
- Profile.tsx - User profile management and editing
- Landing.tsx - Public landing page
- Login.tsx - Authentication login
- Register.tsx - User registration
- Onboarding.tsx - New user onboarding flow
- Help.tsx - FAQ and documentation
- CashFlowChart.tsx - Heavy chart component (Recharts)
- ForecastChart.tsx - Heavy chart component (Recharts)
- Layout.tsx - Main layout wrapper with navigation
- NotificationCenter.tsx - Notification dropdown
- ProfileDropdown.tsx - Profile menu dropdown
- ProtectedRoute.tsx - Route protection wrapper
| Page | Chart Type | Library | Size Impact | Notes |
|---|---|---|---|---|
| Dashboard | CashFlowChart (ComposedChart) | Recharts | HIGH | 📊 Multiple series (current, w1, w2, w3, expected, forecast, risk) |
| Forecast | ForecastChart (ComposedChart + Area) | Recharts | HIGH | 📈 Monte Carlo analysis with P10, P50, P90 bands |
CashFlowChart.tsx:
- Uses:
ComposedChart,Bar,Line,XAxis,YAxis,CartesianGrid,Tooltip,Legend,ReferenceLine - Features: Live data polling (10s interval), filtering, date range selection, CSV/PDF export
- Estimated Size: ~50-80KB (minified)
ForecastChart.tsx:
- Uses:
ComposedChart,Area,Line,XAxis,YAxis,CartesianGrid,Tooltip,Legend,ReferenceLine - Features: Live data polling (15s interval), time range selection, metrics display
- Estimated Size: ~50-80KB (minified)
- DecisionEngine - Complex modals, report generation, payment execution
- Invoices - Heavy table rendering (8-24 rows)
- Transactions - CSV file upload, AI draft generation
- Settings - Multiple connected accounts, API configuration
- Preferences - Three-tab interface (notifications, display, financial)
App.tsx
├── Landing.tsx (public)
├── Login.tsx (public)
├── Register.tsx (public)
├── Help.tsx (public)
└── ProtectedRoute
├── Onboarding.tsx
└── Layout
├── Dashboard (with CashFlowChart)
├── Transactions
├── Forecast (with ForecastChart)
├── DecisionEngine
├── ActionComposer
├── Invoices
├── Vendor/:vendorName
├── Settings
├── Profile
├── Preferences
Static Imports (Bundle Impact):
- All 15 pages imported at app root in
App.tsx - All 6 components imported directly
- Recharts fully loaded even when not viewing chart pages
- html2canvas & jsPDF loaded at compile time
Dynamic Imports (Already Optimized):
exportElementToPdf()function uses dynamic imports:const [{ default: html2canvas }, { jsPDF }] = await Promise.all([ import('html2canvas'), import('jspdf') ]);
Chart Libraries:
Recharts Package Size:
- recharts: ~150-200KB (minified)
- Used only in: Dashboard, Forecast (2 of 15 pages = 13%)
- Bundle Impact: Loaded for all users, used by 2 pages
PDF Export Utilities:
html2canvas: ~100KB
jsPDF: ~200KB
Total: ~300KB (minified)
Current Status: ✅ Already using dynamic imports in lib/export.ts
Impact: Only loaded when user clicks "Export to PDF"
Recommendation: Good pattern - follow this for other heavy modules
Pages for Code Splitting (Estimated Sizes):
| Page | Size Est. | Why Lazy Load | Priority |
|---|---|---|---|
| Forecast | 80-120KB | Large chart + table + metrics | 🔴 HIGH |
| Dashboard | 50-100KB | CashFlowChart + modals | 🔴 HIGH |
| DecisionEngine | 60-90KB | Complex interface, modals | 🔴 HIGH |
| Invoices | 40-70KB | Large table rendering | 🟡 MEDIUM |
| Transactions | 40-70KB | Table + CSV upload + AI | 🟡 MEDIUM |
| Settings | 30-50KB | Forms + account list | 🟡 MEDIUM |
| Preferences | 30-50KB | Tab interface + forms | 🟡 MEDIUM |
| ActionComposer | 25-40KB | Forms + API calls | 🟢 LOW |
| VendorDetail | 20-40KB | Detail view + metrics | 🟢 LOW |
| Profile | 20-40KB | Form + edit mode | 🟢 LOW |
| Help | 15-25KB | FAQ static content | 🟢 LOW |
| Landing | 15-25KB | Static content | 🟢 LOW |
| Login | 15-25KB | Form + auth | 🟢 LOW |
| Register | 15-25KB | Form + validation | 🟢 LOW |
| Onboarding | 15-25KB | Form + auth | 🟢 LOW |
Current bundle will include:
✓ react (core) - 50KB
✓ react-dom - 70KB
✓ react-router-dom - 50KB
✓ recharts - 150-200KB (used by 2 pages!)
✓ tailwindcss - ~30KB compiled
✓ All 15 pages statically imported
✓ All 6 components statically imported
✓ html2canvas - already lazy (good!)
✓ jsPDF - already lazy (good!)
Estimated Initial Bundle: 500-800KB (before gzip)
After gzip: ~150-250KB
-
Dynamic imports for PDF generation (export.ts):
// Only loaded when PDF export is triggered const [{ default: html2canvas }, { jsPDF }] = await Promise.all([ import('html2canvas'), import('jspdf') ]);
-
API abstraction layer (api.ts):
- Clean fetch wrapper with error handling
- Single point for CORS and authentication
- Makes API methods easily tree-shakeable
- No route-based code splitting - All pages in initial bundle
- Recharts loaded for all users - Only needed by 2 pages
- No component lazy loading - No
React.lazy() - Vite config missing optimization - No build optimizations
Vite Config (vite.config.ts):
// Current - no optimizations
export default defineConfig({
plugins: [react()],
})
// Missing:
// - build.rollupOptions.output.manualChunks
// - build.chunkSizeWarningLimit
// - build.minify configurationInitial Load (No Code Splitting):
├── React Core: ~50KB
├── React DOM: ~70KB
├── React Router: ~50KB
├── Recharts: ~150-200KB ⚠️ (not used by 87% of pages)
├── Page Components: ~250-350KB
├── UI Components: ~50KB
├── TailwindCSS (compiled): ~30KB
├── Other utilities: ~50KB
└── Fonts (@fontsource): ~150KB
Total: ~900KB-1.2MB (uncompressed)
After Gzip: ~250-350KB
Per-Page Load Time Impact:
- First Contentful Paint (FCP): ~2-3s
- Time to Interactive (TTI): ~4-6s
Initial Load:
├── React Core + Router + DOM: ~170KB
├── Layout + Navigation: ~30KB
├── Auth Context: ~10KB
├── Common Utilities: ~50KB
├── Fonts: ~150KB
└── Inline Landing Page: ~20KB
Total: ~430KB (uncompressed)
After Gzip: ~130-150KB
Lazy Loaded (On-Demand):
├── Dashboard chunk: ~80KB
├── Forecast chunk: ~100KB
├── DecisionEngine chunk: ~70KB
├── Recharts (only for chart pages): ~150KB
└── Other pages: ~250KB
Performance Improvement: 65-70% reduction in initial bundle size
-
Lazy load pages using Route-based code splitting
- Use
React.lazy()+Suspense - Apply to all 15 pages
- Expected savings: 250-350KB
- Use
-
Lazy load chart components
- Load Recharts only for Dashboard/Forecast
- Expected savings: 150-200KB
-
Extract chart code into separate chunks
CashFlowChartcan be tree-shakenForecastChartcan be tree-shaken
-
Optimize vite.config.ts
- Add manual chunk splitting
- Configure minification
- Set chunk size warnings
-
Lazy load heavy pages
- Invoices, Transactions, Settings, Preferences
- Expected savings: 100-150KB
-
Review export patterns
- Keep dynamic imports for PDF (already good)
- Add dynamic imports for other utilities
-
Monitor bundle size
- Add
rollup-plugin-visualizer - Set up bundle size checks in CI/CD
- Add
frontend/src/
├── pages/ [15 pages - all heavy]
│ ├── Dashboard.tsx [Heavy - Chart]
│ ├── Forecast.tsx [Heavy - Chart]
│ ├── DecisionEngine.tsx [Heavy - Modals]
│ ├── Invoices.tsx [Medium - Table]
│ ├── Transactions.tsx [Medium - Upload]
│ ├── Settings.tsx [Medium - Forms]
│ ├── Preferences.tsx [Medium - Tabs]
│ └── ... 8 more pages
│
├── components/ [6 reusable components]
│ ├── CashFlowChart.tsx [Heavy - Recharts]
│ ├── ForecastChart.tsx [Heavy - Recharts]
│ ├── Layout.tsx [Core navigation]
│ └── ... 3 more
│
├── lib/
│ ├── api.ts [API abstraction - good]
│ ├── export.ts [Dynamic imports - good]
│ └── supabaseClient.ts
│
└── context/
└── AuthContext.tsx [Auth state - core]
{
"recharts": "^3.8.1", // 🔴 150-200KB - Lazy load!
"react-router-dom": "^7.13.2", // 50KB - Core
"react": "^19.2.4", // 50KB - Core
"react-dom": "^19.2.4", // 70KB - Core
"html2canvas": "^1.4.1", // ✅ Already lazy-loaded
"jspdf": "^3.0.3", // ✅ Already lazy-loaded
"lucide-react": "^1.6.0", // 20-30KB - Icon library
"@fontsource/*": "^5.x.x", // 150KB total - Fonts
"tailwind-merge": "^3.5.0", // ~5KB
"clsx": "^2.1.1" // ~2KB
}- No Chart.js - Using Recharts (good choice)
- No heavy UI frameworks - Using TailwindCSS (good)
- Service worker: None detected
- PWA features: Not implemented
- Lighthouse Performance Score: 50-60/100
- First Contentful Paint (FCP): 2-3 seconds
- Time to Interactive (TTI): 4-6 seconds
- Total Blocking Time (TBT): 300-500ms
- Lighthouse Performance Score: 75-85/100
- First Contentful Paint (FCP): 1-1.5 seconds (40-50% improvement)
- Time to Interactive (TTI): 2-3 seconds (50-60% improvement)
- Total Blocking Time (TBT): 100-200ms
// App.tsx - All 15 pages loaded upfront
import Dashboard from './pages/Dashboard';
import Forecast from './pages/Forecast';
// ... all 15 pages imported// lib/export.ts - Good dynamic import usage
const [{ default: html2canvas }, { jsPDF }] = await Promise.all([
import('html2canvas'),
import('jspdf')
]);- Review session memory for previous implementation notes
- Create lazy loading implementation plan
- Set up bundle analysis tools
- Implement route-based code splitting
- Test performance improvements
- Update CI/CD with bundle size checks