Lighthouse displays a failed audit when a significant time is taken to execute all the JavaScript on your app, so how do you optimize this and more?

Have you seen a message like this when you generate lighthouse report for an application?

This is caused by sending large JavaScript payloads which in turn impacts the speed of your site significantly. Instead of shipping all the JavaScript to your user as soon as the first page of your application is loaded, split your bundle into multiple pieces and only send what's necessary at the very beginning.
Many JavaScript frameworks bundle all dependencies into one large file. This makes it easy to add your JavaScript to an HTML page. The bundle requires only one link tag, and there are fewer calls needed to set up the page since all the JavaScript is in one place. In theory, bundling JavaScript in this way should speed up page loads and lower the amount of traffic that page needs to handle.
Code splitting is a common practice in large React applications, and the increase in speed it provides can determine whether a user continues using a web application or leaves. Many studies have shown that pages have less than three seconds to make an impression with users, so shaving off even fractions of a second could be significant. Therefore, aiming for three seconds or less of load time is ideal.
Popular module bundlers like webpack, allow you to split your bundles using dynamic imports. For example;
// math.jsexport function subtract(a, b) {return a - b;}
//index.jsimport { subtract } from './math.js';console.log(subtract(26, 16)); // 10
import("./math").then(math => {console.log(math.subtract(26, 16)); //10});
When Webpack comes across this syntax, it automatically starts code-splitting your app. If you’re using Create React App, this is already configured for you and you can start using it immediately. It’s also supported out of the box in Next.js.
There are several ways to implement code splitting in React. Different bundlers work in different ways, but React has multiple methods to customize bundling regardless of the bundler used.
The Dynamic import syntax works for both static site generation and server-side rendering. Dynamic imports use the then function to import only the code that is needed. Any call to the imported code must be inside that function (as seen above and below).
import("./math").then(math => {console.log(math.subtract(26, 16)); //10});
React.lazy allows for lazy loading of imports in many contexts. It is not yet available for server-side rendering, but its diversity of functions makes up for that. The React.lazy method makes it easy to code-split a React application on a component level using dynamic imports. However, there will always be a slight delay that users have to experience when a code-split component is being fetched over the network, so it's important to display a useful loading state. Using React.lazy with the Suspense component helps solve this problem.
//Without Suspense Fallbackimport React, { lazy } from 'react';const HeaderComponent = lazy(() => import('./HeaderComponent'));const App = () => (<div><HeaderComponent /></div>)
//With Suspense Fallbackimport React, { lazy, Suspense } from 'react';const HeaderComponent = lazy(() => import('./HeaderComponent'));const App = () => (<Suspense fallback={<div>Loading...</div>}><HeaderComponent /></Suspense>)
//Suspense Fallback With Multiple Componentsimport React, { lazy, Suspense } from 'react';const HeaderComponent = lazy(() => import('./HeaderComponent'));const FooterComponent = lazy(() => import('./FooterComponent'));const App = () => (<Suspense fallback={<div>Loading...</div>}><HeaderComponent /><FooterComponent /></Suspense>)
If any module fails to load, for example, due to network failure, we will get an error that can handle these errors with Error Boundaries. Once we have created the Error Boundary, we can use it anywhere above our lazy components to display an error state.
import React, { lazy, Suspense } from 'react';const HeaderComponent = lazy(() => import('./HeaderComponent'));const FooterComponent = lazy(() => import('./FooterComponent'));class ErrorBoundary extends React.Component {constructor(props) {super(props);this.state = {hasError: false};}static getDerivedStateFromError(error) {return {hasError: true};}render() {if (this.state.hasError) {return <p>Loading failed! Please reload.</p>;}return this.props.children;}}const App = () => (<ErrorBoundary><Suspense fallback={<div>Loading</div>}><HeaderComponent /><FooterComponent /></Suspense></ErrorBoundary>)
With the above resource, I hope you're better suited to optimize your React application and have what it takes to split a project.
If you are unsure where to begin applying code splitting to your React application, follow these steps:
👉🏾 Learn more about me
👉🏾 Connect on LinkedIn
👉🏾 Subscribe to my blog, let's feast
QOTD: Whatever is worth doing at all, is worth doing well
I'm Jide, a Full-Stack Software Engineer with penchant for Web/App development. I like scratching my own itch and writing about Web Technologies, UI/UX case studies, and Tech-bits thereof. During my spare time, I'm an autodidact polymath acquiring knowledge from various resources online.
© 2025 Jide Abdul-Qudus. All rights reserved.