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
Explain different strategies for code splitting in React. How would you implement route-based code splitting?
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.