
The dashboard that shrank 74% — and the bundle that dropped 64% without losing anything
The monster component
There was a file in Capivara that everyone (me, the agent, anyone opening the repo) avoided: Dashboard.tsx with 994 lines.
It wasn’t a component. It was a condominium — project stats, health check, Umami, Portifolio status, Dogwalk stats, invites, 2FA, admin… all crammed into a single file.
Every new feature added another useEffect, another useState, another JSX block. And the worst part: the initial bundle weighed 668 kB — the browser downloaded EVERYTHING before showing ANYTHING.
The context: 668 kB to the first pixel
The problem wasn’t just readability. It was performance:
- The browser downloaded 668 kB of JavaScript (gzip) before rendering the dashboard’s first pixel
- On 3G/mobile, that was seconds of white screen
- Every admin tab (Dogwalk, Umami, Overview, Notifications…) loaded together — even if the user never opened it
The refactor had 2 goals:
- Dashboard 994 → ~260 lines (extract components)
- Bundle 668 → ~240 kB (load only what’s needed)
The struggle: extract, split, lazy-load
Step 1 — Extract components. The Dashboard was a God Component. Each section became its own component: StatusPage, 7 admin components (Dogwalk, Umami, Overview, Notifications, Logs, Tracking, TwoFA), interactive Recharts graph, toast system, ThemeToggle, SearchBar.
// Before: 994 lines in one file
// After: small components, each with a single responsibility
import { DogwalkAdmin } from "./admin/DogwalkAdmin";
import { UmamiAdmin } from "./admin/UmamiAdmin";
import { OverviewAdmin } from "./admin/OverviewAdmin";
Step 2 — React Router + lazy loading. Instead of loading everything in the initial bundle, each route loads on demand:
// Before: everything in the initial bundle (668 kB)
// After: only the current route loads
const AdminPage = lazy(() => import("./AdminPage"));
const WSLPanel = lazy(() => import("./WSLPanel"));
Step 3 — remove window.location. Manual navigation (window.location = ...) reloaded the whole page. React Router v7 does SPA navigation — only swaps the component, no reload.
The resolution: less is more
Post-refactor numbers:
| Metric | Before | After | Δ |
|---|---|---|---|
| Dashboard.tsx | 994 lines | 262 lines | -74% |
| AdminPage.tsx | 1,091 lines | 97 lines | -91% |
| Initial bundle | 668 kB | 238 kB | -64% |
| Routing | window.location (reload) |
React Router v7 (SPA) | — |
| New components | — | StatusPage + 7 admin | — |
And the best part: no functionality was lost. The dashboard kept everything — stats, health, Umami, Dogwalk, invites, 2FA — just organized and loading on demand.
Metrics
| Metric | Value |
|---|---|
| Dashboard.tsx | 994 → 262 lines (-74%) |
| AdminPage.tsx | 1,091 → 97 lines (-91%) |
| Initial bundle | 668 kB → 238 kB (-64%) |
| Frontend tests | 257/257 passing |
| Backend tests | 131/131 passing |
| TypeScript | 0 errors |
Lessons learned
-
A giant component isn’t a feature — it’s debt — 994 lines in one file is a sign that responsibilities are mixed. Extracting by single responsibility makes code maintainable.
-
Lazy loading is the easiest form of performance — the initial bundle dropped 64% without optimizing ONE line of logic. Just loading what the current route needs.
-
window.locationis the silent villain — manual navigation reloads the whole page (loses state, re-fires requests). React Router does SPA navigation: swaps component, preserves state. -
Refactoring doesn’t have to lose functionality — 994→262 lines with everything working (257 tests passing). The goal is organization + performance, not cutting features.