Code Splitting
Code splitting lets you split your bundle into smaller chunks that load on demand.
React.lazy
Load components only when needed:
jsx
1import { lazy, Suspense } from 'react'
2
3// Instead of static import
4// import Dashboard from './Dashboard'
5
6// Use lazy loading
7const Dashboard = lazy(() => import('./Dashboard'))
8const Settings = lazy(() => import('./Settings'))
9const Profile = lazy(() => import('./Profile'))
10
11function App() {
12 return (
13 <Suspense fallback={<LoadingSpinner />}>
14 <Routes>
15 <Route path="/dashboard" element={<Dashboard />} />
16 <Route path="/settings" element={<Settings />} />
17 <Route path="/profile" element={<Profile />} />
18 </Routes>
19 </Suspense>
20 )
21}Suspense Boundaries
Control loading states with Suspense:
jsx
1function App() {
2 return (
3 <div>
4 {/* Global fallback */}
5 <Suspense fallback={<FullPageLoader />}>
6 <Header />
7
8 {/* Section-specific fallback */}
9 <Suspense fallback={<SidebarSkeleton />}>
10 <Sidebar />
11 </Suspense>
12
13 <main>
14 <Suspense fallback={<ContentSkeleton />}>
15 <MainContent />
16 </Suspense>
17 </main>
18 </Suspense>
19 </div>
20 )
21}Route-Based Splitting
The most common pattern:
jsx
1import { lazy, Suspense } from 'react'
2import { Routes, Route } from 'react-router-dom'
3
4// Lazy load all route components
5const Home = lazy(() => import('./pages/Home'))
6const Products = lazy(() => import('./pages/Products'))
7const ProductDetail = lazy(() => import('./pages/ProductDetail'))
8const Cart = lazy(() => import('./pages/Cart'))
9const Checkout = lazy(() => import('./pages/Checkout'))
10const Admin = lazy(() => import('./pages/Admin'))
11
12function App() {
13 return (
14 <Suspense fallback={<PageLoader />}>
15 <Routes>
16 <Route path="/" element={<Home />} />
17 <Route path="/products" element={<Products />} />
18 <Route path="/products/:id" element={<ProductDetail />} />
19 <Route path="/cart" element={<Cart />} />
20 <Route path="/checkout" element={<Checkout />} />
21 <Route path="/admin/*" element={<Admin />} />
22 </Routes>
23 </Suspense>
24 )
25}
26
27function PageLoader() {
28 return (
29 <div className="flex items-center justify-center h-screen">
30 <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500" />
31 </div>
32 )
33}Named Exports
For components with named exports:
jsx
1// Component with named export
2export function Dashboard() { ... }
3
4// Lazy load with named export
5const Dashboard = lazy(() =>
6 import('./Dashboard').then(module => ({
7 default: module.Dashboard
8 }))
9)Preloading Components
Load before user needs them:
jsx
1const Dashboard = lazy(() => import('./Dashboard'))
2
3// Preload on hover
4function NavLink() {
5 const preload = () => {
6 import('./Dashboard')
7 }
8
9 return (
10 <Link to="/dashboard" onMouseEnter={preload}>
11 Dashboard
12 </Link>
13 )
14}
15
16// Preload after initial render
17useEffect(() => {
18 const timer = setTimeout(() => {
19 import('./Dashboard')
20 import('./Settings')
21 }, 2000)
22
23 return () => clearTimeout(timer)
24}, [])Heavy Libraries
Split large dependencies:
jsx
1// ❌ Loads chart library with initial bundle
2import { Chart } from 'chart.js'
3
4// ✅ Load only when needed
5const ChartComponent = lazy(() => import('./ChartComponent'))
6
7function Dashboard() {
8 const [showChart, setShowChart] = useState(false)
9
10 return (
11 <div>
12 <button onClick={() => setShowChart(true)}>Show Chart</button>
13
14 {showChart && (
15 <Suspense fallback={<ChartSkeleton />}>
16 <ChartComponent />
17 </Suspense>
18 )}
19 </div>
20 )
21}Modal Lazy Loading
jsx
1const Modal = lazy(() => import('./Modal'))
2
3function App() {
4 const [showModal, setShowModal] = useState(false)
5
6 return (
7 <div>
8 <button onClick={() => setShowModal(true)}>Open Modal</button>
9
10 {showModal && (
11 <Suspense fallback={null}>
12 <Modal onClose={() => setShowModal(false)} />
13 </Suspense>
14 )}
15 </div>
16 )
17}Error Boundaries
Handle loading failures:
jsx
1class ErrorBoundary extends React.Component {
2 state = { hasError: false }
3
4 static getDerivedStateFromError(error) {
5 return { hasError: true }
6 }
7
8 render() {
9 if (this.state.hasError) {
10 return (
11 <div>
12 <h2>Failed to load component</h2>
13 <button onClick={() => window.location.reload()}>
14 Reload page
15 </button>
16 </div>
17 )
18 }
19
20 return this.props.children
21 }
22}
23
24// Usage
25<ErrorBoundary>
26 <Suspense fallback={<Loading />}>
27 <LazyComponent />
28 </Suspense>
29</ErrorBoundary>Bundle Analysis
Analyze your bundle size:
bash
1npm install -D source-map-explorer
2
3# Add to package.json scripts
4"analyze": "source-map-explorer 'build/static/js/*.js'"Vite Dynamic Imports
Vite automatically code-splits on dynamic imports:
jsx
1// Vite splits this into a separate chunk
2const Component = lazy(() => import('./Component'))
3
4// Named chunks for better debugging
5const Dashboard = lazy(() =>
6 import(/* webpackChunkName: "dashboard" */ './Dashboard')
7)Code splitting dramatically improves initial load time!
