JL
Johan Lorck
PERFORMANCE
January 25, 20258 min read

How to optimize the performance of your Next.js application

Next.js is today one of the most popular React frameworks for creating fast, performant web applications optimized for SEO.

However, using Next.js doesn't automatically guarantee optimal performance. Good practices are necessary to exploit its full potential.

In this article, we'll explore the main strategies for optimizing the performance of a Next.js application, both on the server side and the client side.

1. Choose the right rendering method

Next.js offers several rendering modes, and the choice has a direct impact on performance. It's a bit like choosing between cooking a dish in advance or preparing it to order in a restaurant.

Static Site Generation (SSG)

Analogy: Like preparing all dishes in advance and serving them instantly.

  • Generates pages at build time
  • Ultra-fast loading time (pages served from a CDN)
  • Ideal for mostly static pages (blog, landing pages, portfolios)
export async function getStaticProps() {
  const data = await fetchData();
  return { props: { data } };
}

In practice: Use SSG for a company blog. Articles are generated once at build time, then served instantly. Result: loading in < 500ms.

Server Side Rendering (SSR)

Analogy: Like preparing each dish to order, fresh but slower.

  • Generates the page on each request
  • Useful for highly dynamic content (dashboard, real-time data)
  • More expensive in server resources and slower

Warning: SSR slows down response time because each visit requires server-side generation. Only use it if data changes constantly.

Incremental Static Regeneration (ISR)

Analogy: The best of both worlds – prepare in advance, but refresh regularly.

  • Combines SSG performance and SSR data freshness
  • Page regeneration after a certain delay (e.g., every 60 seconds)
  • Ideal for e-commerce, news, regularly updated content
export async function getStaticProps() {
  const data = await fetchData();
  return {
    props: { data },
    revalidate: 60 // Regenerates every 60 seconds
  };
}

In practice: An e-commerce site with ISR: product pages are fast like SSG, but prices/inventory are refreshed every minute automatically.

Tip: Favor SSG or ISR whenever possible for optimal performance.

2. Optimize image loading

Images often represent the largest portion of a page's weight (sometimes 70% of the total weight). A poorly optimized image can ruin your performance.

Use the next/image component

Next.js provides an <Image> component that automatically optimizes your images. It's like having an assistant who resizes and compresses each image for you.

  • Automatic lazy loading: images only load when they become visible
  • Smart resizing: adapts size based on screen (mobile vs desktop)
  • Modern formats: automatically converts to WebP or AVIF (up to 30% lighter)
  • No CLS: reserves space to avoid layout shift
Before (classic tag):
<img src="/hero.png" alt="Hero" />
After (next/image):
import Image from "next/image";

<Image
  src="/hero.png"
  alt="Hero"
  width={1200}
  height={600}
  priority // For "above the fold" images
/>

Result: On an e-commerce site, switching from <img> to <Image> reduced the page weight from 3.2 MB to 800 KB, and the LCP (Largest Contentful Paint) went from 4.2s to 1.1s.

3. Reduce JavaScript sent to the client

Less JavaScript = faster loading. Every kilobyte of JavaScript must be downloaded, parsed, and executed by the browser. This is expensive, especially on mobile.

Dynamic Imports (on-demand loading)

Instead of loading all code at once, load certain components only when they're needed. It's like only taking tools out of the garage when you need them.

Before (everything loads at startup):
import HeavyComponent from "./HeavyComponent";

export default function Page() {
  return <HeavyComponent />;
}
After (dynamic loading):
import dynamic from "next/dynamic";

const HeavyComponent = dynamic(() => import("./HeavyComponent"), {
  ssr: false, // Don't load server-side
  loading: () => <p>Loading...</p>
});

export default function Page() {
  return <HeavyComponent />;
}

Use case: A live chat component? Load it only when the user clicks the "Support" button. Gain: -150 KB of JavaScript at initial load.

Remove unnecessary code

  • Avoid heavy libraries

    Example: moment.js (232 KB) → use date-fns (13 KB) or dayjs (7 KB)

  • Clean up unused dependencies

    Check your package.json regularly with npx depcheck

  • Use tree-shaking

    Import only what you need: import {useEffect} from 'react' instead of import * as React

4. Leverage caching intelligently

Caching allows you to avoid redoing the same work multiple times. It's like saving your answers so you don't have to recalculate every time.

Server-side caching

  • Use HTTP headers (Cache-Control)
  • Enable caching with ISR
  • Use a CDN (Vercel, Cloudflare)

Client-side caching

  • Use SWR or React Query
  • Data cached automatically
  • Background revalidation
Example with SWR:
import useSWR from 'swr';

const { data } = useSWR('/api/data', fetcher);
// Data is cached automatically
// No unnecessary reloading

Result: Fewer network requests, smoother navigation, improved user experience.

5. Optimize fonts and CSS

Optimized fonts with next/font

Next.js 13+ includes next/font which automatically optimizes loading of Google Fonts or custom fonts.

  • Faster loading (self-hosted fonts)
  • Less CLS (Cumulative Layout Shift) – text doesn't "jump" when loading
  • No external request to Google Fonts
import { Inter } from "next/font/google";

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

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

Efficient CSS

  • Use CSS Modules or Tailwind CSS

    Avoids conflicts, generates only necessary CSS

  • Remove unused CSS

    Tailwind does this automatically with PurgeCSS

  • Avoid heavy global CSS files

    Prefer component-scoped styles

6. Monitor and analyze performance

You can't improve what you don't measure. Use these tools to monitor your performance.

Recommended tools

  • Lighthouse (in Chrome DevTools)
  • Web Vitals (@vercel/analytics library)
  • Next.js Analytics (on Vercel)
  • Chrome DevTools (Performance tab)

Metrics to monitor

  • LCP (Largest Contentful Paint)

    Time to display main content (< 2.5s)

  • CLS (Cumulative Layout Shift)

    Visual stability (< 0.1)

  • INP (Interaction to Next Paint)

    Interaction responsiveness (< 200ms)

Tip: Aim for a Lighthouse score of 95+ on all metrics (Performance, Accessibility, SEO, Best Practices).

7. Deploy on suitable infrastructure

Hosting choice strongly influences performance. An optimized Next.js app on a poor server will remain slow.

Infrastructure best practices

Use a CDN

Your static files (images, CSS, JS) are distributed globally. Users retrieve files from the nearest server. Result: 3-5x faster loading.

Deploy on Vercel or Netlify

These platforms are optimized for Next.js: edge functions, integrated CDN, intelligent caching, ultra-fast builds.

Enable edge rendering

With Vercel Edge Functions, your code executes closest to the user (latency < 50ms).

Conclusion

Optimizing the performance of a Next.js application doesn't rely on a single technique, but on a combination of best practices:

  • Choose the right rendering mode (SSG, ISR > SSR)
  • Optimize images with next/image
  • Reduce JavaScript with dynamic imports
  • Leverage caching (server + client)
  • Monitor Web Vitals regularly
  • Deploy on optimized infrastructure (Vercel, CDN)

By applying these tips, you'll provide your users with a faster, smoother, and better-ranked experience.

Take action!

Start by auditing your site with Lighthouse, identify the 2-3 priority optimizations, and implement them gradually. The results will be immediately visible.

JL

Johan Lorck

Freelance Next.js developer specializing in creating high-performance optimized websites. I help businesses leverage the full potential of Next.js.

Let's discuss your project →