Why? Improves first‑paint times without complicating component logic.
Every import
you add bundles more JavaScript that the browser must parse before your app can paint. If you always load everything up front—charts, admin panels, rarely‑used modals—first‑time users wait on code they never touch. Conversely, if you sprinkle React.lazy
around every little component you can create a waterfall of tiny requests that actually slows things down. Good code splitting strikes a balance: defer non‑essential chunks (whole routes, big libraries) behind a single Suspense
boundary so the shell loads fast and the user sees meaningful UI immediately.
// App.tsx — route‑level code splitting
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { Suspense, lazy } from "react";
import HomePage from "./pages/HomePage";
const AdminPage = lazy(() => import("./pages/AdminPage")); // heavy chunk
const ReportsPage = lazy(() => import("./pages/ReportsPage")); // heavy chart lib
export default function App() {
return (
<BrowserRouter>
<Suspense fallback={<p className="p-8">Loading page…</p>}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/admin/*" element={<AdminPage />} />
<Route path="/reports/*" element={<ReportsPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
Why this is good
Suspense
fallback handles loading UI for all lazy routes.// App.tsx — everything imported eagerly
import HomePage from "./pages/HomePage";
import AdminPage from "./pages/AdminPage"; // ⬅ loads React‑Table, rich‑text editor…
import ReportsPage from "./pages/ReportsPage"; // ⬅ loads charting library
export default function App() {
return (
<BrowserRouter>
{/* first paint delayed by 500 KB+ of admin/report code the user may never see */}
…
</BrowserRouter>
);
}
// HeavyChart.tsx split into too many micro‑chunks
const Axis = lazy(() => import("./HeavyChart/Axis"));
const Legend = lazy(() => import("./HeavyChart/Legend"));
const Bars = lazy(() => import("./HeavyChart/Bars"));
/* dozens more… */
export default function HeavyChart() {
return (
<Suspense fallback={<p>Chart loading…</p>}>
<Axis />
<Legend />
<Bars /> {/* → dozens of small network requests */}
</Suspense>
);
}
// Each sub‑component triggers its own HTTP request; blocking until
// *all* arrive often feels slower than loading one consolidated chunk.
Suspense
boundary per split group: Keeps loading UI simple and avoids nested spinners.