Skip to content
Next.js8 min read

Profiling and Fixing Slow Next.js Apps

A field guide to diagnosing slow Next.js applications: measuring properly, hunting server waterfalls, shrinking bundles and knowing which fix actually moved the needle.

Slow is a diagnosis, not a symptom. When a client tells us their Next.js app feels sluggish, the first job is to find out which kind of slow: a server that takes ages to respond, a bundle that takes ages to hydrate, images that arrive late, or navigation that stutters. Each has different causes and different fixes, and optimising the wrong one burns weeks for nothing.

Measure before you touch anything

Lab tools lie in both directions — your development machine is faster than your users' phones, and localhost has no network latency. Start with field data: the Chrome UX Report for public pages, or real-user monitoring via the useReportWebVitals hook wired to your analytics. Three numbers tell you where to dig:

  • Poor TTFB points at the server: slow data fetching, missed caching, or cold starts
  • Poor LCP with a healthy TTFB points at images, fonts or render-blocking resources
  • Poor INP points at too much JavaScript doing too much work on the main thread
The three numbers point at three different rooms of the house — do not start renovating before you know which room you are in.

Hunt the server-side waterfalls

In App Router codebases the most common server-side sin is sequential awaits for independent data — three queries at 200 milliseconds each becoming a 600-millisecond response. Turn on the fetch logging option in next.config to see every request, its duration and its cache status printed during development. Fix independent fetches with Promise.all, and push slow-but-secondary data behind a Suspense boundary so the shell streams immediately while the slow part catches up.

ts
// next.config.ts — see what every fetch is doing
const nextConfig = {
  logging: {
    fetches: { fullUrl: true },
  },
};

Shrink the client bundle

Hydration cost scales with shipped JavaScript, and shipped JavaScript grows one innocent import at a time. Run the bundle analyser and look for the usual suspects: a charting library on every page because it lives in a shared component, a Markdown renderer imported at the top level, moment with every locale attached. The fixes are mechanical — move 'use client' boundaries down the tree, and load heavy components lazily where they are actually used.

tsx
import dynamic from 'next/dynamic';

const RevenueChart = dynamic(
  () => import('./RevenueChart'),
  { loading: () => <ChartSkeleton />, ssr: false }
);

Interaction slowness — a poor INP — usually shares the same root cause: too much JavaScript on the main thread. Beyond shrinking the bundle, look for state updates that re-render enormous trees, synchronous work inside event handlers, and effects that fire on every keystroke. The React DevTools profiler makes the expensive re-renders embarrassingly visible.

Images, fonts and third parties

If LCP is the problem, it is almost always the hero image or a web font. Check the LCP image has priority and an honest sizes attribute. Load fonts through next/font so they are self-hosted and applied without a flash of invisible text, and subset them while you are there — most projects ship glyphs no page ever uses. Then audit third-party scripts: the tag manager, the chat widget, the heat-mapping tool. Load them with next/script using the lazyOnload strategy where possible, and be prepared to argue that some of them should simply go.

Verify the win

Every fix ends the same way: re-measure against the number that started the investigation, on the field data, not just in Lighthouse. Performance work without a before-and-after is folklore. Keep the wins in a short log — it builds the case for the next round of budget and stops regressions going unnoticed. Numbers persuade stakeholders; adjectives do not.

Got a Next.js app that has slowed down and nobody knows why? Performance audits are bread-and-butter work for STRCLI — get in touch and bring your field data.

Start your project

Have an idea? Let's ship it together.

Tell us what you're building — we'll reply within one business day with an honest take and a clear next step.