Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 175
Advanced

Next.js Performance Optimization

Profile apps, optimize bundle size, and implement advanced caching and streaming.

TL;DR

  1. 01Use dynamic imports to split code and reduce bundles.
  2. 02Enable compression and minification in production.
  3. 03Implement streaming for faster first contentful paint.

Tips

  1. 01PPR works best for pages where the majority of content is static but a small slice (cart count, user greeting, recommendations) is dynamic. Pages that are fully dynamic benefit more from standard SSR.

Warnings

  1. 01Never use a <link rel="preconnect"> to Google Fonts alongside next/font/google — next/font downloads and self-hosts the font at build time so the Google CDN is never hit in production.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 176
Advanced

Next.js Performance Optimization

(continued)

Bundle Size Analysis

  • Check bundle size with @next/bundle-analyzer.
    npm install -D @next/bundle-analyzer
    
  • Configure next.config.js to analyze bundles.
    const withBundleAnalyzer = require("@next/bundle-analyzer")({
      enabled: process.env.ANALYZE === "true",
    });
    
    module.exports = withBundleAnalyzer({});
    
  • Run analysis to find large dependencies.
    ANALYZE=true npm run build
    
  • Look for duplicate or unused dependencies to remove.
  • Use ANALYZE=true npm run build to open an interactive bundle treemap in the browser.
  • Confirm bundle wins actually improve real load times by measuring Core Web Vitals (LCP, INP, CLS) with the web-vitals library or a Lighthouse run, not bundle size alone.
    npx lighthouse https://your-app.com --view
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 177
Advanced

Next.js Performance Optimization

(continued)

Code Splitting Strategies

  • Use dynamic imports to split code at route level.
    import dynamic from "next/dynamic";
    
    const HeavyComponent = dynamic(() => import("./HeavyComponent"));
    
    export default function Page() {
      return <HeavyComponent />;
    }
    
  • Split large components loaded conditionally.
    const Editor = dynamic(() => import("./Editor"), {
      loading: () => <p>Loading editor...</p>,
    });
    
  • Lazy load images with the Image component.
    import Image from "next/image";
    
    <Image
      src="/hero.jpg"
      alt="Hero"
      loading="lazy"
      width={1200}
      height={600}
    />
    
  • Optimize barrel imports from large libraries with optimizePackageImports nested under the top-level experimental key in next.config.js — it is still an experimental flag, so the wrapper is required for it to take effect.
    // next.config.js
    module.exports = {
      experimental: {
        optimizePackageImports: ["lodash", "date-fns"],
      },
    };
    
  • Use barrel file tree-shaking to avoid importing entire libraries.
    // Bad: imports entire lodash
    import _ from "lodash";
    // Good: imports only what you need
    import debounce from "lodash/debounce";
    
  • Measure whether your splitting actually helped by reporting Core Web Vitals (LCP, CLS, INP) from the client with useReportWebVitals from next/web-vitals, or by enabling Vercel Speed Insights for real-user production data.
    // app/_components/web-vitals.tsx
    "use client";
    import { useReportWebVitals } from "next/web-vitals";
    
    export function WebVitals() {
      useReportWebVitals((metric) => {
        console.log(metric); // send to your analytics endpoint instead
      });
      return null;
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 178
Advanced

Next.js Performance Optimization

(continued)

Partial Prerendering

  • Enable Partial Prerendering (PPR) in Next.js 15 to serve a static shell instantly while streaming dynamic slots.

    // next.config.js
    module.exports = {
      experimental: { ppr: true }
    };
    
  • Wrap only the dynamic parts of a page in <Suspense> — the static shell is served from the CDN edge, dynamic content streams in.

    // app/product/[id]/page.tsx
    import { Suspense } from "react";
    import { ProductDetails } from "./ProductDetails"; // static — pre-rendered
    import { RecommendedProducts } from "./RecommendedProducts"; // dynamic
    
    export default function ProductPage() {
      return (
        <div>
          <ProductDetails />   {/* served immediately from CDN */}
          <Suspense fallback={<p>Loading recommendations...</p>}>
            <RecommendedProducts /> {/* streamed after static shell */}
          </Suspense>
        </div>
      );
    }
    
  • Mark a component as dynamic using connection() from next/server — this opts it out of static prerendering.

    import { connection } from "next/server";
    
    export async function Cart() {
      await connection(); // forces this component to be dynamic
      const cart = await fetchCart();
      return <ul>{cart.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
    }
    
  • PPR is composable — you can have multiple <Suspense> slots per page, each streaming independently.

  • PPR reduces Time To First Byte (TTFB) for pages with mixed static and dynamic content compared to full SSR.

Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 179
Advanced

Next.js Performance Optimization

(continued)

Compression and Minification

  • Enable Gzip compression for responses.
    // next.config.js
    module.exports = {
      compress: true, // enabled by default
    };
  • Use React strict mode to catch performance issues.
    // next.config.js
    module.exports = {
      reactStrictMode: true,
    };
  • Minify HTML, CSS, and JavaScript automatically.
    npm run build
  • Check production bundle size after optimization.
  • Remove console statements in production with compiler options.
    // next.config.js
    module.exports = {
      compiler: { removeConsole: { exclude: ["error"] } }
    };
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 180
Advanced

Next.js Performance Optimization

(continued)

Image and Font Optimization

  • Use the <Image> component for automatic format conversion, resizing, and lazy loading.

    import Image from "next/image";
    
    <Image
      src="/hero.jpg"
      alt="Hero banner"
      width={1200}
      height={600}
      priority          // preload above-the-fold images; omit for below-fold
      quality={80}      // default is 75; lower for smaller files
      sizes="(max-width: 768px) 100vw, 50vw"  // tell browser which size to download
    />
    
  • Set priority on hero images to preload them — only use it for images visible without scrolling.

  • Use the sizes prop so the browser downloads the smallest image that fits the layout.

    // Full-width on mobile, half-width on desktop
    <Image src="/photo.jpg" alt="Photo" fill sizes="(max-width: 640px) 100vw, 50vw" />
    
  • Load Google Fonts with zero layout shift using next/font/google.

    import { Inter } from "next/font/google";
    
    const inter = Inter({
      subsets: ["latin"],
      display: "swap",   // avoids invisible text during font load
      variable: "--font-inter"
    });
    
    export default function RootLayout({ children }) {
      return <html className={inter.variable}><body>{children}</body></html>;
    }
    
  • Self-host a local font with next/font/local to eliminate the external Google Fonts network request.

    import localFont from "next/font/local";
    const myFont = localFont({ src: "./fonts/GeistVF.woff2", display: "swap" });
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 181
Advanced

Next.js Performance Optimization

(FAQ)

FAQ

Run ANALYZE=true next build with @next/bundle-analyzer configured in next.config.js to get an interactive treemap of your client and server bundles. Look for large node_modules being included in client chunks — these are usually the easiest wins.

Use next/dynamic for components that aren't needed on initial render — modals, charts, below-the-fold sections, and anything heavy like rich text editors. Static imports are fine for everything rendered in the initial viewport, since Next.js already tree-shakes and splits by route automatically.

SSG generates pages at build time (no revalidation), ISR adds revalidate to regenerate pages in the background after a set interval, and SSR runs on every request with no built-in cache unless you add Cache-Control headers manually. For most content pages, ISR with a short revalidation window gives the best balance of freshness and performance.

Yes, Next.js enables gzip compression by default when running next start, but it's intentionally disabled when you deploy behind a reverse proxy like Nginx or a CDN — those should handle compression instead to avoid double-encoding. If you're on a platform like Vercel, Brotli compression is applied automatically at the edge.

Wrap slow data-fetching components in <Suspense> with a fallback — Next.js will stream the shell HTML immediately and flush each suspended section as it resolves. For page-level streaming, loading.tsx files in the App Router act as automatic Suspense boundaries for the entire route segment.