Reducing JavaScript Bundle Size in the Dashboard
Reducing JavaScript Bundle Size in the Dashboard
Release: v1.0.56 · Control: PERF-05 · Category: Frontend Performance
Background
The Blockboss dashboard is composed of a number of large, feature-rich client components — each covering a distinct area of block management such as budgeting, bank reconciliation, GDPR compliance, Irish legislation references, sinking funds, planned preventative maintenance (PPM), and general maintenance.
Before code splitting was applied, every one of these modules was included in a single monolithic JavaScript bundle that was downloaded by the browser on the very first page load — regardless of which section of the dashboard the user actually navigated to.
The Problem
The combined uncompressed source size of the primary dashboard client components exceeded 700 KB:
| Component | Source Size |
|---|---|
budget/client.tsx | 72 KB |
irish-legislation/client.tsx | 66 KB |
gdpr/client.tsx | 64 KB |
bank-reconciliation/client.tsx | 62 KB |
sinking-fund/client.tsx | 58 KB |
ppm/client.tsx | 54 KB |
maintenance/client.tsx | 53 KB |
This created an unnecessarily large initial JavaScript payload, increasing parse and execution time and delaying when the dashboard became interactive for users — particularly on slower connections or lower-powered devices.
Solution
1. Dynamic Imports via PERF-01
The primary resolution for PERF-05 is the application of dynamic imports across all large dashboard client components. This work was introduced as part of PERF-01 (code splitting) and ensures that each module's JavaScript is only fetched from the network when the user navigates to that section of the dashboard.
For example, instead of eagerly importing the budget module at startup:
// Before — eagerly loaded, always in the bundle
import BudgetClient from './budget/client';
It is loaded dynamically when needed:
// After — loaded on demand via dynamic import
const BudgetClient = dynamic(() => import('./budget/client'), {
loading: () => <LoadingSpinner />,
});
This pattern is applied consistently across all large dashboard client components.
2. Lazy-Loaded Sub-Components
Beyond route-level code splitting, an internal audit of the largest client components identified UI panels that are only ever rendered in response to a specific user interaction (opening a detail view, clicking a record, etc.). These are ideal candidates for an additional layer of lazy loading:
service-charges/arrears-detail-panel.tsx (32 KB)
This panel is only displayed when a property manager drills into an individual arrears record. Since it is never visible on initial render, its 32 KB of JavaScript does not need to be included in the parent component's chunk.
budget-detail-panel.tsx (41 KB)
This panel is only displayed when a user opens a specific budget entry for detailed review. At 41 KB, deferring its load until the panel is opened provides a meaningful reduction in the initial chunk size for the budget module.
These sub-components are extracted and loaded lazily, typically using a pattern like:
const ArrearsDetailPanel = dynamic(
() => import('./arrears-detail-panel'),
{ ssr: false }
);
Performance Impact
| Metric | Effect |
|---|---|
| Initial JS payload | Significantly reduced — modules load on demand |
| Time to Interactive (TTI) | Improved — less JS to parse and execute on load |
| Largest Contentful Paint (LCP) | Improved — browser is not blocked by unused module code |
| Sub-component payloads | Deferred until user interaction (modals, detail panels) |
Affected Files
src/app/dashboard/budget/client.tsxsrc/app/dashboard/bank-reconciliation/client.tsxsrc/app/dashboard/gdpr/client.tsxsrc/app/dashboard/irish-legislation/client.tsxsrc/app/dashboard/sinking-fund/client.tsxsrc/app/dashboard/ppm/client.tsxsrc/app/dashboard/maintenance/client.tsxsrc/app/dashboard/service-charges/arrears-detail-panel.tsxsrc/app/dashboard/budget/budget-detail-panel.tsx
Related
- PERF-01 — Dynamic imports / route-level code splitting (prerequisite for this fix)
- PERF-05 — JavaScript bundle size control (this release)