How to optimise a Next.js website
Where the time actually goes, and the handful of changes that move the numbers.
UT Studio8 min read
Most Next.js performance advice optimises things that were never the bottleneck. Before changing anything, find out where the time goes — on the deployed origin, not on localhost, where everything is instant and nothing is compressed the way it will be.
Measure first, and measure the right thing
- Run Lighthouse against production, not the dev server. The dev server is a different application.
- Use the network panel with throttling on. A 4G profile is closer to your median user than your laptop is.
- Look at the build output. Next prints per-route JavaScript; the routes at the top of that list are where to work.
Ship less JavaScript
The largest single lever, and it is architectural rather than a setting. Every component that does not need state, an effect or a browser API should be a Server Component — and then it costs zero bytes.
// Client boundary at the leaf, not the branch.
export function ProductPage({ product }) {
return (
<article>
<Overview product={product} /> {/* server */}
<Specs product={product} /> {/* server */}
<AddToCart offers={product.offers} /> {/* 'use client' */}
</article>
);
}A client boundary is inherited
Everything imported by a `use client` component becomes client code too. One `use client` at the top of a page ships the whole page — this is the most common cause of a bundle nobody can explain.
Fonts
Self-host through `next/font`. It removes a DNS lookup and a connection to a third party on the critical path, and it generates the size-adjust descriptors that stop the layout jumping when the real font arrives.
import { Inter } from 'next/font/google';
export const sans = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-sans',
});Images
- Use `next/image` so dimensions are reserved and modern formats are served.
- Set `priority` on the one image in the initial viewport, and on no others.
- Give every image explicit dimensions. Reserved space is the whole of layout stability.
- Do not lazy-load the hero image. Lazy-loading the largest contentful paint delays the metric it is measured by.
Render statically wherever the content allows
| If the page | Then | Cost per request |
|---|---|---|
| Is the same for everyone | Static | None |
| Has a known set of routes | SSG with params | None after build |
| Reads search params or a session | Dynamic | Real — make it fast |
What usually does not help
- Adding a state management library. It is not the bottleneck and it is more client JavaScript.
- Micro-optimising server render. It is normally a few milliseconds against hundreds spent on the network.
- Prefetching everything. It competes for bandwidth with what the user is actually waiting for.
- Replacing a working CSS approach with a faster one. Stylesheets are rarely the problem.
Re-measure after every change
Performance work without measurement is decoration. Half of the changes people are confident about turn out to be neutral.
Templates mentioned here
Everything above is written against real products. These are the ones this page draws on.

