Explain different strategies for code splitting in React. How would you implement route-based code splitting?
4 minadvancedreactsystem-designstrategiescodesplitting
Quick Answer
Code splitting is a technique to split your code into smaller chunks that can be loaded on demand, improving initial load performance.
Detailed Answer
Code splitting is a technique to split your code into smaller chunks that can be loaded on demand, improving initial load performance.
5.2.1. Code Splitting Strategies
-
Route-Based Code Splitting
import { lazy, Suspense } from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; // Lazy load route components const Home = lazy(() => import('./pages/Home')); const About = lazy(() => import('./pages/About')); const Contact = lazy(() => import('./pages/Contact')); const Dashboard = lazy(() => import('./pages/Dashboard')); const App = () => ( <BrowserRouter> <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="/contact" element={<Contact />} /> <Route path="/dashboard" element={<Dashboard />} /> </Routes> </Suspense> </BrowserRouter> ); -
Component-Based Code Splitting
import { lazy, Suspense, useState } from 'react'; const HeavyChart = lazy(() => import('./HeavyChart')); const DataTable = lazy(() => import('./DataTable')); const Dashboard = () => { const [showChart, setShowChart] = useState(false); const [showTable, setShowTable] = useState(false); return ( <div> <button onClick={() => setShowChart(true)}> Load Chart </button> <button onClick={() => setShowTable(true)}> Load Table </button> {showChart && ( <Suspense fallback={<div>Loading chart...</div>}> <HeavyChart /> </Suspense> )} {showTable && ( <Suspense fallback={<div>Loading table...</div>}> <DataTable /> </Suspense> )} </div> ); }; -
Library-Based Code Splitting
// Split large libraries const loadMoment = () => import('moment'); const loadLodash = () => import('lodash'); const DateFormatter = () => { const [moment, setMoment] = useState(null); useEffect(() => { loadMoment().then(({ default: moment }) => { setMoment(moment); }); }, []); if (!moment) return <div>Loading date formatter...</div>; return <div>{moment().format('YYYY-MM-DD')}</div>; };
5.2.2. Advanced Code Splitting Patterns
-
Preloading with Intersection Observer
const usePreloadOnIntersection = (importFn: () => Promise<any>) => { const [isLoaded, setIsLoaded] = useState(false); const ref = useRef<HTMLDivElement>(null); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting && !isLoaded) { importFn().then(() => setIsLoaded(true)); observer.disconnect(); } }, { threshold: 0.1 } ); if (ref.current) { observer.observe(ref.current); } return () => observer.disconnect(); }, [importFn, isLoaded]); return { ref, isLoaded }; }; const LazyComponent = () => { const { ref, isLoaded } = usePreloadOnIntersection( () => import('./HeavyComponent') ); return ( <div ref={ref}> {isLoaded ? ( <Suspense fallback={<div>Loading...</div>}> <HeavyComponent /> </Suspense> ) : ( <div>Component will load when visible</div> )} </div> ); }; -
Dynamic Imports with Error Boundaries
class ChunkLoadErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, retryCount: 0 }; } static getDerivedStateFromError(error) { if (error.name === 'ChunkLoadError') { return { hasError: true }; } return null; } componentDidCatch(error, errorInfo) { if (error.name === 'ChunkLoadError') { console.error('Chunk load failed:', error); } } retry = () => { this.setState(prevState => ({ hasError: false, retryCount: prevState.retryCount + 1 })); }; render() { if (this.state.hasError) { return ( <div> <h2>Failed to load component</h2> <button onClick={this.retry}> Retry (Attempt {this.state.retryCount + 1}) </button> </div> ); } return this.props.children; } } const App = () => ( <ChunkLoadErrorBoundary> <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/dashboard" element={<Dashboard />} /> </Routes> </Suspense> </ChunkLoadErrorBoundary> ); -
Conditional Code Splitting
const ConditionalComponent = ({ userRole }: { userRole: string }) => { const [AdminPanel, setAdminPanel] = useState<React.ComponentType | null>(null); useEffect(() => { if (userRole === 'admin') { import('./AdminPanel').then(({ default: AdminPanelComponent }) => { setAdminPanel(() => AdminPanelComponent); }); } }, [userRole]); if (userRole === 'admin' && AdminPanel) { return ( <Suspense fallback={<div>Loading admin panel...</div>}> <AdminPanel /> </Suspense> ); } return <div>Regular user content</div>; };
5.2.3. Webpack Configuration for Code Splitting
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
enforce: true,
},
},
},
},
};
5.2.4. Performance Monitoring
const useChunkLoadTime = () => {
const [loadTimes, setLoadTimes] = useState<Record<string, number>>({});
const trackChunkLoad = (chunkName: string) => {
const startTime = performance.now();
return () => {
const endTime = performance.now();
const loadTime = endTime - startTime;
setLoadTimes(prev => ({
...prev,
[chunkName]: loadTime
}));
// Send to analytics
analytics.track('chunk_loaded', {
chunk: chunkName,
loadTime,
timestamp: Date.now()
});
};
};
return { loadTimes, trackChunkLoad };
};
// Usage
const Dashboard = () => {
const { trackChunkLoad } = useChunkLoadTime();
useEffect(() => {
const endTracking = trackChunkLoad('dashboard');
return endTracking;
}, [trackChunkLoad]);
return <div>Dashboard content</div>;
};
5.2.5. Best Practices
-
Bundle Analysis
# Analyze bundle size npm install --save-dev webpack-bundle-analyzer npx webpack-bundle-analyzer build/static/js/*.js -
Loading States
- Always provide meaningful loading states
- Consider skeleton screens for better UX
- Implement retry mechanisms for failed chunks
-
Preloading Strategy
- Preload critical routes on user interaction
- Use
<link rel="prefetch">for likely next pages - Implement intelligent preloading based on user behavior
-
Error Handling
- Wrap lazy components in error boundaries
- Provide fallback UI for chunk load failures
- Implement retry mechanisms
This comprehensive approach ensures optimal performance while maintaining a great user experience.