Measuring performance in web apps using tools such as Google Lighthouse provides developers with insights into how well a site adheres to best practices. Lighthouse offers detailed feedback on performance, accessibility, and SEO, helping teams optimize their sites for both user experience and search rankings. This Performance score is calculated by combining several different performance metrics.
|Metric||Description||Weight (Lighthouse 10)|
|First Contentul Paint (FCP)||The time it takes for the first piece of content to appear on the screen from when the page starts loading. It gives an idea about the perceived loading speed for users.||10%|
|Largest Contentful Paint (LCP)||The time it takes for the largest content element (like an image or a text block) on the page to become visible. It's a measure of perceived loading speed and user experience.||25%|
|Total Blocking Time (TBT)||Measures the amount of time that a page is blocked from reacting to user input, like a mouse click.||30%|
|Cumulative Layout Shift (CLS)||Quantifies how often users experience unexpected layout shifts—a low CLS indicates a more stable interface. It's a core metric for understanding visual stability.||25%|
|Speed Index (SI)||Represents how quickly the contents of a webpage are visually populated. A lower Speed Index indicates a faster visual display||10%|
It's crucial to understand that Lighthouse scores might not always be accurate. Some techniques can skew or falsify the results. We recommend using tools like PageSpeed Insights for more reliable feedback. Keep in mind, when using the Lighthouse tool in a browser, the performance can be influenced by the power of your computer.
Code splitting is an optimization technique that uses the next/dynamic feature, built upon
Use dynamic imports when:
- Dealing with large libraries or components not needed immediately.
- Rendering components conditionally (e.g., modals).
- Integrating third-party tools or widgets occasionally.
Each dynamic import results in an extra server request, potentially leading to delays. Striking the right balance is crucial for maintaining peak performance.
Note: Dynamic imports and code splitting in Next.js (and Webpack) work on a file basis. Ensure individual components you intend to load separately are in distinct files. Otherwise, it’ll not work. It works the same way if you export components from one file (the technique used to make imports cleaner).
Here's a simpler breakdown:
But there's a catch. Hydration can slow things down by blocking the main thread, which can increase the Total Blocking Time (TBT). So, while hydration makes a whole page interactive, it can also make it feel a bit slower for a moment. To combat this, some developers use progressive or selective hydration to only activate the most crucial parts of a page, improving both speed and user experience. There’s a cool library next-lazy-hydrate which helps to optimize this process. Basically while using this library you can simultaneously tap into the benefits of both code splitting and progressive hydration. It’s also worth noting that in Next.js 13 app directory you can achieve similar results by streaming server components.
Using Next.js greatly simplifies image optimization with its next/image component. This component automatically configures width, height, and blurDataURL values for imported images, minimizing Cumulative Layout Shift (CLS) before the image fully loads. You can also set these values manually. The component compresses images for better performance, reducing the data browsers need to fetch. It also supports lazy and priority loading, allowing control over which images load first. This results in faster website load times and an enhanced user experience.
So in general it’s a good choice to use next/image in most cases. However, if your app has a lot of images, using that feature on some hosting providers can get expensive (they are optimized during runtime). Remember, next/image isn't the only way to optimize images in Next.js. While it's user-friendly and makes maintaining your app easier, you can always use other standard methods for image optimization such as:
- lazy loading below the fold
fetchpriorityfor critical images
- decoding images asynchronously
In fact, next/image uses these techniques under the hood but requires much less config.
Note: If you're using remote images and your Next.js version is below 12.3, you'll need to define domains in the Next.js config file. If you aim to use images from multiple domains, consider trying our next-image-proxy, which facilitates this.
Next.js's next/font feature streamlines font optimization. It uses the CSS size-adjust property to prevent unexpected layout shifts when loading fonts. Additionally, it has a built-in method for using Google Fonts: instead of fetching them via external requests, Next.js downloads the font and CSS files during the build and bundles them with other web assets like HTML and CSS during deployment. However, as with next/image, there are manual methods to boost the performance such as:
- font-display: swap
- using system fonts instead of external ones
- preloading fonts
Next.js's next/script component offers targeted strategies for loading third-party scripts to optimize performance:
- beforeInteractive: Loads the script before Next.js initializes, potentially adding to initial rendering time but ensuring script availability.
- afterInteractive (default): Loads the script after some page hydration, balancing between immediate functionality and smooth rendering.
- lazyOnload: Waits for browser idle time to load the script, ideal for non-immediate scripts, enhancing perceived performance.
- worker (experimental): Loads the script in a web worker, offloading execution to keep the main browser thread responsive. While promising, it's still in the testing phase (it uses Partytown under the hood).
It also offers enhanced flexibility by allowing users to integrate callback functions like onLoad, onReady, and onError. These callbacks can be crucial in handling script behaviors and responding to different loading scenarios. To dive deeper and understand its full capabilities, refer to the official Next.js documentation.
In Next.js, code is segmented into modules and bundled by webpack into single files for execution in the browser. As apps grow in complexity, their codebase can become bulky, affecting load times and maintainability. Analyzing these bundles helps pinpoint optimization opportunities.
To enhance performance, consider:
- Eliminating duplicate or unused npm packages.
- Moving some dependencies to the server bundle
For developers looking to enhance the performance of their Next.js apps, bundle analysis is an essential step. The @next/bundle-analyzer is a Next.js library designed to inspect the size and structure of your app's bundle. It helps pinpoint bulky code segments, allowing you to trim down the bundle size and improve the performance.
Optimizing performance for Next.js applications is essential but can be complex. Luckily, Next.js provides tools to help streamline this task. For larger projects, it's important to plan optimization by declaring performance budget, since not all techniques will suit every context. For those who want more detailed information, we'll be preparing an article focused on how to set and stick to a performance budget. With proper planning and the right tools, boosting performance becomes easier, leading to more satisfied users.
Empower your vision, partner with us today