Ivaan Information Technology
Mastering Next.js App Router Performance
← Back to Blog
Tech Insights

Mastering Next.js App Router Performance

✍By Sony Daman
πŸ—“Dec 15, 2025
⏱5 min read

The Next.js App Router gives you strong performance defaults, and it is also easy to undo them with a few careless choices. This guide covers the changes that deliver the biggest gains, in the order we usually apply them to client projects.

Keep components on the server

Components in the app directory are server components by default. They send no component JavaScript to the browser. Add "use client" only to the small components that need state, effects or browser APIs, and keep it off layouts and whole pages. Check your bundle: if a page ships 300 KB of JavaScript for a mostly static layout, a large client boundary is the likely cause.

Fetch in parallel and stream

Do independent fetches together:

const [product, reviews] = await Promise.all([getProduct(id), getReviews(id)]);

For slower sections, wrap them in Suspense or add a loading.tsx file so Next.js streams the shell first and fills in the rest when data arrives. Users see a meaningful page sooner, and Largest Contentful Paint improves.

Understand caching

Next.js has several caches: request memoization during a render, the data cache, the full route cache and the client-side router cache. Defaults changed in Next.js 15, where fetch requests and GET route handlers are no longer cached by default. Set your intent :

// Cache for an hour
fetch(url, { next: { revalidate: 3600, tags: ["products"] } });

// After an admin update
import { revalidateTag } from "next/cache";
revalidateTag("products");

For pages that rarely change, such as blog posts, use static generation with generateStaticParams and time-based or on-demand revalidation. A static page served from the CDN beats any server-rendered response. Reserve dynamic rendering for pages that depend on the request, like a logged-in dashboard. Reading cookies() or headers() makes a route dynamic, so keep those calls out of pages that should stay static.

Optimize images

Use next/image. It serves modern formats, resizes per device and prevents layout shift. Provide width and height or use fill with a sized parent, set a correct sizes attribute, and add priority to the LCP image only. Overusing priority cancels its benefit.

Load fonts without layout shift

next/font downloads font files at build time, hosts them with your site and applies size-adjusted fallbacks. Load only the weights and subsets you use. Each extra weight adds a download.

Control third-party scripts

Use next/script with strategy="afterInteractive" or "lazyOnload" for analytics, chat and tag managers. Audit them every quarter, because each one adds main-thread work that hurts Interaction to Next Paint. Remove tags that nobody reads reports from.

Split code

Each route is its own bundle already. For heavy components that appear below the fold or after a click, such as charts, rich text editors and maps, use next/dynamic:

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

Import only what you use from large libraries, prefer lightweight date and utility libraries, and enable optimizePackageImports for icon and component packages that re-export many modules.

Prefetch navigation

The Link component prefetches routes in the viewport in production. For links to heavy pages that users rarely open, set prefetch={false} to save bandwidth.

Measure with the right tools

  • @next/bundle-analyzer shows which modules fill each bundle.
  • Lighthouse and PageSpeed Insights report lab and field metrics.
  • React DevTools Profiler reveals components that re-render too often.
  • useReportWebVitals sends real user metrics to your analytics.
  • Vercel Speed Insights or a similar real user monitoring tool tracks changes after each release.

Reduce server cost and latency

Deploy close to your database and your users, reuse database connections through a pool or proxy in serverless environments, and avoid heavy synchronous work inside request handlers. Use route handlers and Server Actions only for work that needs them, not for data you could render statically.

Checklist

  1. Move "use client" down the tree.
  2. Parallelize fetches and stream slow sections.
  3. Set explicit cache and revalidate rules.
  4. Use next/image and next/font .
  5. Defer third-party scripts.
  6. Analyze bundles on every major release.

Common questions

Is the App Router faster than the Pages Router?

It can be, mainly through server components that cut client JavaScript and through streaming. A well-built Pages Router app can still outperform a careless App Router one, so measure your own pages before and after migrating.

Why is my page dynamic when I expected static?

Calls to cookies(), headers(), or reading searchParams opt a route into dynamic rendering. The build output marks static routes with a circle and dynamic ones with a lambda symbol. Move request-dependent code into a client component or a smaller dynamic segment.

How do I keep large pages responsive?

Virtualize long lists with a library such as TanStack Virtual, paginate server-side, and avoid passing huge props from server to client components, since that data is serialized into the page.

We build and tune Next.js sites, including this one. If your app feels slow, request a performance audit or view our projects.

Explore Our Services

Need help putting this into practice? See our web, mobile and AI development services, browse projects we have delivered, read the FAQs, or contact our team.