Introduction
React Lazy Loading is a powerful performance optimization technique that helps reduce the initial bundle size of your application by splitting code into smaller chunks and loading them on demand. This guide will show you how to implement lazy loading effectively in your React applications.
Understanding React Lazy Loading
React provides two main features for implementing code-splitting:
-
React.lazy()
: Lets you render a dynamic import as a regular component -
Suspense
: Shows fallback content while waiting for the lazy component to load
Basic Implementation
Simple Component Lazy Loading
import React, { lazy, Suspense } from 'react';
// Instead of regular import
// import ExpensiveComponent from './ExpensiveComponent';
// Use lazy loading
const ExpensiveComponent = lazy(() => import('./ExpensiveComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<ExpensiveComponent />
</Suspense>
);
}
Route-based Lazy Loading
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
// Lazy load route components
const Home = lazy(() => import('./routes/Home'));
const Dashboard = lazy(() => import('./routes/Dashboard'));
const Profile = lazy(() => import('./routes/Profile'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
</Router>
);
}
Advanced Patterns
1. Custom Loading Component
const LoadingSpinner = () => (
<div className="loading-spinner">
<div className="spinner"></div>
<p>Loading content...</p>
</div>
);
// Reusable lazy loading wrapper
const LazyComponent = ({ component: Component, ...props }) => {
return (
<Suspense fallback={<LoadingSpinner />}>
<Component {...props} />
</Suspense>
);
};
// Usage
const MyLazyComponent = lazy(() => import('./MyComponent'));
<LazyComponent component={MyLazyComponent} someProp="value" />;
2. Error Boundary Integration
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Lazy loading error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>Something went wrong. Please try again.</div>;
}
return this.props.children;
}
}
// Usage with lazy loading
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<MyLazyComponent />
</Suspense>
</ErrorBoundary>
);
}
3. Preloading Components
const MyLazyComponent = lazy(() => import('./MyComponent'));
// Preload component when hovering over a button
function PreloadButton() {
const handleMouseEnter = () => {
const componentPromise = import('./MyComponent');
// Component will start loading on hover
};
return (
<button
onMouseEnter={handleMouseEnter}
onClick={() => setShowComponent(true)}
>
Show Component
</button>
);
}
Best Practices
- Choose the Right Granularity
// Too fine-grained (avoid)
const Button = lazy(() => import('./Button'));
// Better - lazy load feature modules
const FeatureModule = lazy(() => import('./features/FeatureModule'));
- Group Related Components
// Lazy load related components together
const AdminDashboard = lazy(() => import('./admin/Dashboard'));
// This will load all admin components in one chunk
- Handle Loading States Gracefully
const LoadingFallback = () => (
<div className="loading-state">
<Skeleton /> {/* Use skeleton loading */}
<ProgressBar /> {/* Show loading progress */}
</div>
);
function App() {
return (
<Suspense fallback={<LoadingFallback />}>
<MyLazyComponent />
</Suspense>
);
}
Common Patterns and Use Cases
1. Modal/Dialog Lazy Loading
const Modal = lazy(() => import('./Modal'));
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<Suspense fallback={<LoadingSpinner />}>
<Modal onClose={() => setIsOpen(false)} />
</Suspense>
)}
</>
);
}
2. Conditional Feature Loading
function FeatureFlag({ flag, children }) {
const LazyFeature = lazy(() =>
flag ? import('./NewFeature') : import('./OldFeature')
);
return (
<Suspense fallback={<LoadingSpinner />}>
<LazyFeature>{children}</LazyFeature>
</Suspense>
);
}
Performance Tips
- Chunk Naming
// Use webpack magic comments for better debugging
const AdminPanel = lazy(() =>
import(/* webpackChunkName: "admin" */ './AdminPanel')
);
- Loading Priority
// High-priority routes
const MainContent = lazy(() =>
import(/* webpackPrefetch: true */ './MainContent')
);
// Lower-priority features
const Analytics = lazy(() =>
import(/* webpackPreload: true */ './Analytics')
);
Common Pitfalls to Avoid
- Don't lazy load components that are always needed on initial render
- Avoid lazy loading very small components
- Don't forget to handle loading and error states
- Be careful with nested Suspense boundaries
Monitoring and Analytics
const trackComponentLoad = (componentName) => {
// Track loading time and success
performance.mark(`${componentName}-start`);
return {
success: () => {
performance.mark(`${componentName}-end`);
performance.measure(
`${componentName}-load`,
`${componentName}-start`,
`${componentName}-end`
);
},
error: (error) => {
// Log error to analytics
console.error(`Failed to load ${componentName}:`, error);
}
};
}
// Usage
const MyComponent = lazy(() => {
const tracking = trackComponentLoad('MyComponent');
return import('./MyComponent')
.then(module => {
tracking.success();
return module;
})
.catch(error => {
tracking.error(error);
throw error;
});
});
Conclusion
React Lazy Loading is an essential tool for optimizing large React applications. By following these patterns and best practices, you can significantly improve your application's initial load time and overall performance.
Top comments (0)