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.