Lazy Loading Strategies for Large React Applications
When your React app grows past 50 components, lazy loading becomes essential. Here are proven patterns for code splitting routes, components, and data.
You ship a new feature, run Lighthouse, and watch your performance score drop by 15 points. The bundle grew by another 80KB. Users on mobile connections wait 4 seconds before anything interactive loads. This is the scaling wall that every large React application hits.
Lazy loading is the solution, but the strategy matters more than the syntax. Let's look at the patterns that actually work at scale.
Route-Level Code Splitting
The highest-impact lazy loading happens at the route level. In Next.js App Router, this is automatic — each page segment is its own chunk. But if you're using React Router or a custom setup, you need explicit code splitting:
import { lazy, Suspense } from "react";
import { Routes, Route } from "react-router-dom";
// Each route is a separate chunk
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));
const Analytics = lazy(() => import("./pages/Analytics"));
const UserManagement = lazy(() => import("./pages/UserManagement"));
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/analytics" element={<Analytics />} />
<Route path="/users" element={<UserManagement />} />
</Routes>
</Suspense>
);
}
The key insight: users only visit one route at a time, so loading all routes upfront wastes bandwidth on pages they may never see.
Component-Level Lazy Loading
Below route-level, target heavy components that aren't immediately visible: modals, drawers, tabs beyond the first, and below-the-fold widgets.
import dynamic from "next/dynamic";
// Modal — only loaded when user clicks "Delete"
const DeleteConfirmModal = dynamic(
() => import("@/components/modals/delete-confirm"),
{ ssr: false }
);
// Tab content — only loaded when tab is selected
const AdvancedSettings = dynamic(
() => import("@/components/settings/advanced"),
{ loading: () => <TabSkeleton /> }
);
// Chart library — heavy dependency, only on analytics page
const RevenueChart = dynamic(
() => import("@/components/charts/revenue"),
{ ssr: false, loading: () => <ChartPlaceholder /> }
);
Prefetching: Loading Before the User Clicks
Lazy loading creates a trade-off: smaller initial bundle but a delay when navigating. Prefetching eliminates this trade-off by loading chunks before the user needs them.
On hover prefetch: When the user hovers over a navigation link, start loading that route's chunk. Most users hover for 200–400ms before clicking — enough time to load a typical chunk.
On viewport prefetch: Use IntersectionObserver to prefetch components when they scroll near the viewport. Load the comment section when the user scrolls to 70% of the article.
After idle prefetch: Use requestIdleCallback to prefetch likely next routes after the current page finishes loading. If 80% of dashboard visitors go to analytics next, prefetch analytics during idle time.
Data-Level Lazy Loading
Don't forget about data. Loading all data upfront for a page with multiple sections is wasteful:
- Use
Suspenseboundaries around data-fetching Server Components to stream sections independently - Implement infinite scroll or "Load More" instead of fetching 500 items at once
- Defer non-critical data fetches until the user interacts (expand a section, click a tab)
Measuring the Impact
Track these metrics before and after implementing lazy loading:
- Initial JS bundle size — should decrease 40–60% for large apps
- Time to Interactive (TTI) — the primary metric lazy loading improves
- First Contentful Paint (FCP) — faster if you're also lazy-loading heavy CSS
- Navigation timing — ensure prefetching keeps route transitions under 100ms
Takeaways
- Start with route-level splitting — it delivers the biggest impact with the least effort
- Lazy-load modals, non-active tabs, and below-the-fold components next
- Pair lazy loading with prefetching to eliminate navigation delays
- Don't forget data — stream Server Components and paginate large lists
- Measure TTI and bundle size to quantify the improvement
Admin
Cal.com
Open source scheduling — tự host booking system, thay thế Calendly. Free & privacy-first.
Bình luận (0)
Đăng nhập để bình luận
Chưa có bình luận nào. Hãy là người đầu tiên!