</>StackKit
</>StackKit

Developer tutorials & guides

Performance metrics dashboard
Next.js

Next.js Performance Optimization: 10 Techniques That Actually Work

Speed up your Next.js app with image optimization, code splitting, caching headers, bundle analysis, and rendering strategy choices.

P
Priya Nair
April 8, 20258 min read
#nextjs#performance#react#web-vitals

Why Next.js Performance Matters

Core Web Vitals directly affect your Google search ranking. Slow sites lose users — 53% of mobile visitors abandon a page that takes over 3 seconds to load. Next.js ships with powerful optimization tools, but they only work if you use them correctly.

Here are 10 techniques that make the biggest real-world difference.


1. Use next/image for All Images

The <Image> component automatically:

  • Converts to WebP/AVIF (30-50% smaller than JPG/PNG)
  • Lazy loads images below the fold
  • Prevents layout shift with explicit dimensions
  • Serves the right size for each device
import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero banner"
  width={1200}
  height={600}
  priority  // add for above-the-fold images
  quality={85}
/>

Never use <img> directly in Next.js.


2. Choose the Right Rendering Strategy

  • Static Generation (default) — Pre-renders at build time. Fastest possible. Use for anything that doesn't change per-request.
  • ISR (Incremental Static Regeneration) — Static but auto-refreshes on a schedule. Best for blog posts, product pages.
  • Server Components — Renders on server per-request. Good for dynamic, personalized content.
  • Client Components — Only for interactivity (forms, state, event handlers).
// ISR — revalidate every 60 seconds
export const revalidate = 60;

// Force dynamic (server per request)
export const dynamic = 'force-dynamic';

3. Analyze Your Bundle Size

npm install -D @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({});
ANALYZE=true npm run build

This opens a visual tree map of your bundle. Common culprits: moment.js (500KB), lodash (70KB), large icon libraries.


4. Dynamic Import Heavy Components

Don't load code until it's needed:

import dynamic from 'next/dynamic';

const RichTextEditor = dynamic(() => import('./RichTextEditor'), {
  loading: () => <div className="h-64 bg-gray-100 animate-pulse rounded" />,
  ssr: false,  // disable SSR for client-only components
});

5. Optimize Fonts with next/font

Self-host fonts for zero layout shift and no external requests:

import { Inter } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
});

export default function RootLayout({ children }) {
  return (
    <html className={inter.className}>
      {children}
    </html>
  );
}

6. Set Proper Cache Headers

// app/api/products/route.ts
export async function GET() {
  const products = await getProducts();
  return Response.json(products, {
    headers: {
      'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
    },
  });
}

7. Use React Server Components for Data Fetching

Fetch data directly in server components — no extra API roundtrip from the client:

// This component runs on the server — no bundle cost
async function ProductList() {
  const products = await db.product.findMany();  // direct DB access
  return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

8. Minimize Client Components

Mark components with 'use client' only when they actually need browser APIs, state, or event handlers. Everything else should be a server component.


9. Preload Critical Resources

// In your root layout
<link rel="preload" href="/fonts/inter.woff2" as="font" crossOrigin="anonymous" />
<link rel="dns-prefetch" href="https://api.example.com" />
<link rel="preconnect" href="https://api.example.com" />

10. Enable PPR (Partial Prerendering) in Next.js 15+

// next.config.js
module.exports = {
  experimental: {
    ppr: true,
  },
};

PPR lets you have a static shell with dynamic "holes" — the best of static and dynamic in the same page.


Measuring Your Improvements

Always measure before and after:

npx lighthouse https://your-site.com --view

Target scores: LCP < 2.5s, FID < 100ms, CLS < 0.1.


Conclusion

Start with image optimization and rendering strategy — these two changes alone typically move your Lighthouse score by 20-30 points. Then analyze your bundle, defer what you can, and measure again. Performance is iterative, not a one-time fix.

#nextjs#performance#react#web-vitals