Web performance is a crucial part of the user experience of our web application. It has a direct impact on the conversion rate. Walmart found that for every 1-second improvement in page load time, conversions increased by 2% (see source). And if that's not enough, search engines favor fast websites. They rank them higher than slow websites. So improving your web application's performance can increase both the conversion rate and the organic customer acquisition rate.
Web Vitals (coined by the Chrome team) are a set of metrics to measure the performance of a website. Optimizing for these metrics ensures your visitors will enjoy a much better experience.
Core Web Vitals are a subset of the aforementioned Web Vitals that is relevant to any web page. The subset focuses on three performance aspects, loading, interactivity, and visual stability. In this article, we will focus on improving two of them, loading and interactivity. They're represented by these two metrics, Largest Contentful Paint (LCP) and First Input Delay (FID). There are many strategies to improve these metrics, but we'll focus on reducing the bundle size.
Even if we apply best practices such as tree shaking and code splitting in our project, the bundle can become huge. So what should we do? Dynamic import and loading prioritization!
Import after page load
In this section, we will see how we can use React lazy and Next.js dynamic to create a new function that imports a component only after the page is loaded. For simplicity's sake, I'll show how to implement it with Next.js, but the same concept can be easily applied to React lazy or even other frameworks such as Vue.
Let's first create a function that returns a promise that resolves once the page is loaded. A page loading process consists of three phases, loading, interactive, and completed. The function receives an argument that states at what phase we should load the resource. We use the `readystatechange` event to listen to changes in the loading process.
Our `onPageLoad` function first returns a promise as planned. In the promise, we check for the current ready state of the document. It's an important edge case that we have to deal with; otherwise, the promise might never resolve. If the page is already loaded, we resolve the promise. Second, we create a callback function for the event listener and subscribe to the `readystatechange` event. In the callback, we check the new ready state of the document. If it equals the requested state or if the document is completely loaded, we can resolve the promise and unsubscribe.
This was the heavy lifting, and now the only thing that remained to do is to create our new dynamic function that will load the component on page load.
Like Next.js dynamic function, we receive a loader function that returns a promise with a React component and an optional ready state to prioritize the loading. In the function, we use the good-old dynamic function, but before providing the loader function, we chain it to the `onPageLoad` function that we created earlier. This makes sure the import statement will not be called before the page is loaded. The second parameter to the dynamic function disabled evaluating this expression in the server. This is required because we use the document object, which is available only to the client.
We can use our function to make sure our components will be imported after the page load as follows:
`const DynamicComponent = dynamicPageLoad(() => import('./component'))`.