NextjsPerformance Best Practices

Performance Best Practices

Next.js gives you a lot of performance for free out of the box, but the App Router also gives you the tools to actively hurt your own performance — client-side fetching where a Server Component would do, unoptimized images, fonts that cause layout shift. This page is a practical checklist to run through on any real project, roughly ordered from highest to lowest impact.

The checklist
  1. Default to Server Components. Only add 'use client' at the leaves of the tree that actually need interactivity, state, or browser APIs — every client component ships JavaScript to the browser that a server component does not.

  2. Use next/image for every image. It handles responsive sizing, lazy loading, modern formats (WebP/AVIF), and reserves layout space to prevent Cumulative Layout Shift.

  3. Use next/font instead of linking to Google Fonts or other external font hosts. It self-hosts the font files at build time and eliminates the extra network request and layout shift caused by font swapping.

  4. Avoid client-side fetching for data you already have on the server. Fetching in a Server Component and passing data down as props is almost always faster than fetching again after hydration in useEffect.

  5. Stream slow data with Suspense. Wrap slow-loading sections in <Suspense> with a loading.tsx or fallback so the rest of the page can render and become interactive immediately.

  6. Lazy-load heavy, below-the-fold components with next/dynamic so their JavaScript is not part of the initial bundle.

  7. Prefer static rendering or ISR over force-dynamic wherever the data doesn't genuinely need to be live on every request.

  8. Monitor Core Web Vitals (LCP, INP, CLS) in production with real user data, not just local Lighthouse runs, since real devices and networks vary widely.

Server Components by default

TSX
// Server Component (default) — no JS shipped for this component
async function ProductList() {
  const products = await getProducts()
  return (
    <ul>
      {products.map((p) => (
        <ProductRow key={p.id} product={p} />
      ))}
    </ul>
  )
}

// Only the interactive leaf becomes a Client Component
'use client'
function AddToCartButton({ id }: { id: string }) {
  const [pending, setPending] = React.useState(false)
  return (
    <button disabled={pending} onClick={() => setPending(true)}>
      Add to cart
    </button>
  )
}
Streaming with Suspense

A single slow data source shouldn't block the entire page. Wrapping it in Suspense lets Next.js send the fast parts of the page immediately and stream in the slow part once it's ready.

TSX
import { Suspense } from 'react'

export default function Page() {
  return (
    <>
      <Header />
      <Suspense fallback={<ReviewsSkeleton />}>
        <Reviews /> {/* slow fetch, streamed in separately */}
      </Suspense>
    </>
  )
}
Deferring heavy components

TSX
import dynamic from 'next/dynamic'

// Only downloaded when this component actually renders,
// good for below-the-fold widgets like charts or maps
const AnalyticsChart = dynamic(() => import('./AnalyticsChart'), {
  loading: () => <ChartSkeleton />,
})
Quick reference

Problem

Fix

Large, unoptimized images

next/image

Layout shift from web fonts

next/font

Oversized client JS bundle

Server Components + next/dynamic for heavy client parts

One slow fetch blocking the whole page

Suspense boundary around just that section

Rebuilding the same page on every request unnecessarily

Static rendering or ISR (revalidate)

Note
None of these are exotic techniques — they are the default, idiomatic way to build in the App Router. Performance problems in Next.js apps are usually the result of fighting the framework's defaults (adding 'use client' too high in the tree, fetching on the client when server data was already available) rather than a lack of advanced optimization.
Tip
Run through this checklist before reaching for micro-optimizations. Fixing one misplaced 'use client' boundary near the root of your app often has more impact than any amount of manual memoization.