Master performance optimization in React. Learn how to use lazy loading, code splitting, and image optimization to keep your dashboard fast and efficient.
Previously in this course, we explored handling large datasets to keep our interfaces responsive. In this lesson, we shift our focus to the "hidden" side of performance: the assets our users download before they even see a single pixel of our dashboard.
If you aren't careful, your React application will grow into a massive JavaScript bundle that forces users to wait seconds before the first interaction. We will fix this by implementing code splitting, lazy loading, and image optimization.
When you build a dashboard, you likely have components for settings, profile management, and complex data visualizations. If you import all of these at the top of your App.js, your browser must download, parse, and execute the entire codebase before the user can see even the login screen.
This is the primary driver of poor "Time to Interactive" (TTI) metrics. We need to split our code so that the user only downloads what they need for the current view.
React provides a built-in way to defer the loading of components until they are actually rendered. This is called lazy loading.
Instead of a static import, we use React.lazy() to create a dynamic import. When the component is first rendered, React will trigger a network request to fetch the separate JavaScript file.
In our dashboard project, let's lazy load the heavy Reports and Settings pages.
JAVASCRIPTimport React, { Suspense, lazy } from CE9178">'react'; import { BrowserRouter as Router, Routes, Route } from CE9178">'react-router-dom'; // Instead of static imports: // import Reports from CE9178">'./pages/Reports'; // import Settings from CE9178">'./pages/Settings'; // Use lazy loading: const Reports = lazy(() => import(CE9178">'./pages/Reports')); const Settings = lazy(() => import(CE9178">'./pages/Settings')); function App() { return ( <Router> {/* Suspense is required to show a fallback while the chunk loads */} <Suspense fallback={<div>Loading page...</div>}> <Routes> <Route path="/reports" element={<Reports />} /> <Route path="/settings" element={<Settings />} /> </Routes> </Suspense> </Router> ); }
By wrapping your routes in Suspense, you provide a "loading state" while the browser fetches the separate chunk. This significantly reduces the initial bundle size of your main main.js file.
While JavaScript bloat is a major issue, raw, unoptimized images are often the silent killers of web performance. A 5MB dashboard background image will ruin your Core Web Vitals regardless of how well your code is split.
srcset attribute or a library to serve smaller images to mobile devices and high-resolution images to desktops.loading="lazy" attribute on <img> tags.For a deeper dive into managing assets, see Handling Media in React: Optimizing Images and Loading States.
npm run build. Look at the console output to identify your largest JavaScript chunks.React.lazy.Suspense boundary with a loading spinner to ensure the UI doesn't "flicker" while the chunk fetches.Suspense, React will throw an error.fallback UI is a different size than the loaded component, you will trigger Layout Shift, which hurts your SEO and user experience. Ensure your loading skeletons match the dimensions of the final content.We've improved our dashboard's performance by:
As we continue to build, remember that performance is a continuous process. You've already learned how to handle state efficiently in Structuring State for Performance, and now your assets are optimized to match.
Up next: We'll dive into Internationalization (i18n) to make our dashboard accessible to a global audience.
Learn how to manage large datasets in React using pagination, infinite scrolling, and virtualization to maintain high performance and a smooth user experience.
Read moreStop waiting for components to mount before fetching data. Learn how to use React Router loaders to implement prefetching and eliminate request waterfalls.
Optimizing Asset Loading