This article was written by Anel Kalajevac, Technical Lead at HTEC Group
When we talk about web applications, everything comes down to performance. Except for the user experience, web application performance has an impact on bounce rates, conversion rates, and ranking in organic search. In other words, slow web applications will hurt your brand and cost you money. There are many reasons why a web application may load slowly and many different approaches to improve performance. I’m going to show you how to improve the performance of Next.js-based applications by using the code splitting technique and lazy hydration. But first, let’s see what affects web application performance.
What affects web application performance?
The most common reasons why web application load time might be lagging are:
- Large image sizes
- Not using browser cache
- Too many plugins and external libraries
- Slow network connection
- Older browsers
- Poor hosting plan
How to test web application performance?
Many different tools can be used for checking web application performance, but all of them are measuring mainly the same metrics. Alongside many different metrics, the most important are Largest Contentful Paint, First Input Delay, and Cumulative Layout Shift. These metrics are also defined as Core Web Vitals by Google.
For web application performance checking I prefer using PageSpeed Insights, which provides a nice report for mobile and desktop devices. The report includes a general score calculated by Lighthouse Scoring Calculator, results for different metrics, and suggestions for improvements.
The metrics scores and the performance score are colored according to these ranges:
- 0 to 49 (red): Poor.
- 50 to 89 (orange): Needs Improvement.
- 90 to 100 (green): Good.
Now, when we know how to test web application performance, let’s see how it can be improved.
Let’s say that we have a web application created by using React and Next.js framework. PageSpeed Insights reports two main issues:
- Minimize main-thread work.
There are five core parts of implementation:
- A webpack plugin for changing module ids.
- Next.js plugin for marking lazy modules.
- Custom Next.js document.
- Next.js dynamic import.
- Component for lazy hydration (library or custom implementation based on Intersection Observer API).
First, I will create a webpack plugin to change the module id.
Then, I will create a Next.js plugin to mark target modules as lazy and merge a webpack configuration with the Next.js configuration. This plugin will find defined modules for lazy loading and add the prefix “lazy” to the module id. For this purpose, I will use the previously created webpack plugin for changing module ids. The prefix “lazy” is an indicator that the module should be lazy-loaded when it is necessary. In the example below, I’ve defined the footer component as a lazy module.
Now, when I have a logic that marks the footer component as a lazy module, the next step is a modification of Next.js internals within _document.tsx. I will extend next/head to skip printing <script> tags for lazy JS chunks into a document. Also, I will modify NextScript to let a webpack manage the loading of dynamic ids.
As soon as I scroll near the footer component, webpack will download the dynamic import and LazyHydration will hydrate it into interactive react code.