Why Your Next.js App Is Slower Than It Should Be
The most common performance mistakes we see in Next.js projects, and what to do about them without a complete rewrite.
Next.js is fast. The apps built on it are frequently not. The framework does a remarkable amount of heavy lifting, but it can only do so much if the code on top of it is working against it.
These are the issues we find repeatedly when we review projects — none of them exotic, all of them fixable.
Unnecessary Client Components
The most impactful change in the App Router was making components server-side by default. Zero JavaScript sent to the browser for components that do not need interactivity. Data fetching that happens on the server, closer to the database, with no waterfall round trips through the client.
The common mistake is reaching for "use client" whenever something feels slightly dynamic, even when the component in question does not use any browser APIs, state, or event handlers. Every unnecessary client component is JavaScript you are making your users download and execute.
The rule of thumb: push the "use client" boundary as far down the component tree as possible. A page can be a server component even if one small interactive element inside it is a client component.
Data Fetching Waterfalls
A waterfall happens when fetch B cannot start until fetch A completes, and fetch C cannot start until fetch B completes. In a server component, this means your page takes as long as all your data fetches combined rather than as long as the slowest one.
Promise.all and Suspense boundaries are the tools to reach for here. Parallelise everything that can be parallelised, and stream in the parts of the page that are not ready yet rather than blocking the entire render.
Bundle Size Nobody Is Watching
It is easy to install a package without thinking about what it adds to the client bundle. One library pulls in a date utility that includes twelve locale files. Another imports an entire icon set when you are using three icons.
The Next.js bundle analyser is free, takes five minutes to set up, and will almost certainly show you something surprising about where your JavaScript is going. Running it once a quarter as a routine check is a good habit worth building.
Images That Have Not Been Optimised
The next/image component exists specifically to handle the tedious parts of image optimisation — lazy loading, correct sizing, format selection, preventing layout shift. Using a plain <img> tag instead opts out of all of that.
The more common version of this mistake is using next/image correctly but providing images that are far larger than they need to be, which the component will serve regardless.
Getting performance right in Next.js is mostly about understanding what the framework is doing on your behalf and not accidentally undoing it.
If your Next.js app has performance issues you have not been able to track down, get in touch — a technical review is often faster than you expect.