Web and Prosper

BlogBetter Performance in Next.js

Better Performance in Next.js

Next.js offers a variety of built-in tools and patterns to deliver high-performance web applications.

1st April 2025

Next.js offers a variety of built-in tools and patterns to deliver high-performance web applications. Additionally, third-party libraries and best practices can take Lighthouse scores even higher. In this post, we’ll cover some of the top techniques and libraries you can use to supercharge your Next.js app’s performance:

The <Image/> Component

The <Script/> Component

@next/bundle-analyzer

@next/dynamic & Dynamic Imports

@next/font

@bundlesize

Lighthouse / Unlighthouse

The <Image/> Component

What is does

Next.js provides an optimized Image component that automatically handles image resizing, lazy loading, and responsive image generation. This helps avoid layout shifts and speeds up image loading, which is a key factor in Lighthouse’s performance and accessibility scores.

Benefits

  • Responsive and lazy-loaded images out of the box

  • Improved performance and SEO

  • Avoids large layout shifts by reserving the correct space for images

  • Automatic resizing for various device screen sizes

How to apply

Replace your traditional img tags with Next.js’s Image component:

import Image from 'next/image';

export default function HomePage() {
  return (
    <div>
      <h1>My Next.js App</h1>
      <Image
        src="/images/hero.jpg"
        alt="Hero"
        width={1200}
        height={600}
        priority // or remove priority to have it lazy load below-the-fold
      />
    </div>
  );
}

Key configuration notes

  • Make sure you configure the next.config.js if your images come from remote domains.

  • Adjust the priority prop to ensure critical, above-the-fold images load immediately.

The <Script/> Component

What it does

Next.js’s Script component enables you to load external or inline scripts with more fine-grained control over when and how they’re executed. You can specify loading strategies such as beforeInteractive, afterInteractive, or lazyOnload, which helps optimize the load order of scripts.

Benefits

  • Control the order of script execution to prevent blocking the main thread.

  • Potentially reduce Largest Contentful Paint (LCP) time if non-critical scripts are delayed.

  • Minimizes unwanted render-blocking JavaScript.

How to apply

Suppose you have an analytics script that doesn’t need to run immediately:

import Script from 'next/script';

export default function HomePage() {
  return (
    <>
      <h1>Welcome to My App</h1>
      
      <Script
        src="https://example.com/analytics.js"
        strategy="lazyOnload"
        onLoad={() => console.log('Analytics script loaded')}
      />
    </>
  );
}

Key configuration notes

  • strategy="beforeInteractive" runs the script early (before Next.js hydrates).

  • strategy="afterInteractive" runs the script right after hydration.

  • strategy="lazyOnload" defers loading until the browser is idle, ideal for scripts that are non-critical.

@next/bundle-analyzer

What it does

@next/bundle-analyzer is a tool that visualizes the contents of your JavaScript bundle. Analyzing your bundle sizes can reveal large dependencies or unused code, helping you identify optimization opportunities.

Benefits

  • Find which dependencies are taking up the most space.

  • Aids in code-splitting strategies.

  • Directly see how your dynamic imports reduce bundle size.

How to apply

Install package

# or
yarn add @next/bundle-analyzer

Update your next.config.js

const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer({
  // Your Next.js config
});

Run the anlayzer

```ANALYZE=true npm run build

This will produce a local server (often at localhost:8888) to visualize your bundle.

@next/dynamic & Dynamic Imports

What they do

Dynamic imports allow you to import certain components or code paths only when they are needed. Next.js provides the dynamic function from next/dynamic to break up your bundle. This cuts down the initial load size, improving Lighthouse performance metrics like First Contentful Paint (FCP) and Time to Interactive (TTI).

I would say that out of all the techniques mentioned here, this is one of my favourites and can deliver very good improvements.

Benefits

  • Reduces initial JavaScript bundle size.

  • Only loads components for the current route or user action (like a modal).

  • Improves performance on slower devices.

How to apply

import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
  // You can also specify a loading component if you want
  loading: () => <p>Loading...</p>,
});

export default function HomePage() {
  return (
    <div>
      <h1>My Next.js App</h1>
      <HeavyComponent />
    </div>
  );
}

In this snippet, HeavyComponent is split off from the main bundle and only fetched when needed.

@next/font

What it does

The @next/font package (or the newer built-in Next.js Font features as of Next.js 13+) helps optimize custom and Google Fonts usage. It pulls fonts in at build time or from the Google Fonts CDN in a way that reduces layout shifts and ensures efficient font delivery.

Benefits

  • Eliminates external network requests if fonts are hosted locally.

  • Prevents layout shifts from late font loading.

  • Improves Lighthouse’s performance and “Best Practices” scores.

How to apply

Below is an example (using Next.js 13+ approach) of importing an open-sourced Google Font:

import { Roboto } from '@next/font/google';
 
const roboto = Roboto({
  weight: ['400', '700'],
  subsets: ['latin']
});

export default function App({ Component, pageProps }) {
  return (
    <main className={roboto.className}>
      <Component {...pageProps} />
    </main>
  );
}

The font is automatically in-lined and optimized, reducing flicker and improving page speed metrics.

@bundlesize

What it does

@bundlesize is a CLI tool to keep track of your bundle’s size by setting thresholds. If your bundle grows beyond your defined limits, the build will fail or trigger a warning. This encourages continuous performance monitoring.

Benefits

  • Prevents regression in bundle size.

  • Enforces consistent performance checks in your CI/CD pipeline.

  • Encourages devs to remain mindful of large dependencies.

How to apply

Install @bundlesize:

npm install --save-dev @bundlesize
# or
yarn add --dev @bundlesize

Configure in package.json or a .bundlesize file:

{
  "bundlesize": [
    {
      "path": "your-output-directory/**/*.js",
      "maxSize": "300 kB"
    }
  ]
}

Add a script in your package.json:

{
  "scripts": {
    "bundlesize": "bundlesize"
  }
}

Run npm run bundlesize after your build. If your bundle exceeds 300 kB (in the above example), the tool alerts you or fails the build.

Lighthouse / Unlighthouse

What they are

Lighthouse: Google’s open-source tool for auditing performance, accessibility, SEO, and more.

Unlighthouse: A newer tool providing a continuous auditing approach, crawling multiple pages of your site. It can offer a more holistic view than manually testing one page at a time in Lighthouse.

Benefits

  • Identify performance bottlenecks, accessibility issues, and SEO improvements.

  • Track performance across your entire site, not just the home page.

  • Integrates easily into CI/CD or local development for ongoing performance checks.

How to apply Lighthouse

  • Run Lighthouse in Chrome DevTools: open the Lighthouse tab, then click Generate report.

  • Use the Node CLI version of Lighthouse for automated tasks or CI.

How to apply Unlighthouse

Install Unlighthouse:

npm install -g unlighthouse

Run:

unlighthouse http://localhost:3000

Configure advanced settings via a unlighthouse.config.js file to crawl multiple routes or add authentication if needed.

Bringing It All Together

  • Optimize images with Next.js Image for faster loads and improved layout stability.

  • Load scripts intelligently with Script to avoid blocking the main thread.

  • Analyze bundle sizes using @next/bundle-analyzer to discover large dependencies.

  • Code-split with dynamic imports to shrink initial bundle size.

  • Optimize fonts with @next/font to reduce layout shifts and external calls.

  • Enforce size limits via @bundlesize in CI to prevent performance regressions.

  • Regularly audit with Lighthouse or Unlighthouse to maintain and monitor performance across all pages.

By applying these strategies, your Next.js applications will load faster, rank higher, and provide a smoother user experience, all while improving your Lighthouse (or Unlighthouse) scores. Stay vigilant about bundle sizes, use Next.js best practices, and incorporate automated checks so you maintain those strong scores over time.

Happy optimising! With these techniques in your toolbox, your Next.js app should be ready to impress both users and Lighthouse audits alike.