Performance Optimization and Bundle Analysis

Advanced
13 min

Performance Optimization and Bundle Analysis

Next.js gives you fast defaults, but a production application can still ship a megabyte of JavaScript, block rendering on slow queries, or shift layout while fonts load. Fixing that starts with measuring the right things. After this lesson you will be able to read Core Web Vitals, find which dependencies inflate your bundles, and apply the highest-impact optimisations in the right order.

Measure First: Core Web Vitals

Google's Core Web Vitals summarise user experience in three numbers:

| Metric | Measures | Good threshold | Typical Next.js fix | |---|---|---|---| | LCP (Largest Contentful Paint) | Time until the main content is visible | 2.5 s | priority on the hero image, streaming, caching | | INP (Interaction to Next Paint) | Responsiveness to input | 200 ms | Less client JavaScript, useTransition | | CLS (Cumulative Layout Shift) | Unexpected movement | 0.1 | next/image sizes, next/font, sized skeletons |

Run Lighthouse in Chrome DevTools for a lab measurement, then collect field data with useReportWebVitals, as in the sample at the top of this lesson. Field data reveals what real devices on real networks experience, which lab runs on a developer laptop never do.

Find What You Ship: Bundle Analysis

bash
npm install --save-dev @next/bundle-analyzer
typescript
// next.config.ts import type { NextConfig } from "next"; import bundleAnalyzer from "@next/bundle-analyzer"; const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === "true" }); const nextConfig: NextConfig = {}; export default withBundleAnalyzer(nextConfig);

Run ANALYZE=true npm run build and a treemap opens for the client and server bundles. Look for three things: large libraries imported for one function, duplicated packages at different versions, and code that only Server Components should need but that appears in the client graph. The next build summary also prints "First Load JS" per route; anything well above 100 kB for a simple page deserves a look.

Ship Less JavaScript

Most wins come from moving work to the server or deferring it:

  • Keep "use client" at the leaves; a page that is a Client Component drags every import into the browser.
  • Import from the module you need. Barrel files such as import { Check } from "lucide-react" can pull in thousands of icons; Next.js optimises many popular packages automatically, and you can add others:
typescript
// next.config.ts experimental: { optimizePackageImports: ["my-ui-lib"] },
  • Lazy load below-the-fold or on-demand components with next/dynamic, and load third-party scripts with next/script strategies.
  • Prefer native platform features (Intl, fetch, URLSearchParams) over utility libraries that reimplement them.

Speed Up the Server Side

  • Cache what can be cached: fetch options, "use cache", and revalidate windows turn repeated work into static output.
  • Fetch in parallel with Promise.all and stream slow sections inside <Suspense> so TTFB stays low.
  • Place the database close to the server region; a 100 ms round trip repeated five times is half a second.
  • Use after() for logging and notifications so they never delay the response.

Images, Fonts and Layout Stability

next/image with correct width, height or fill and a sizes attribute prevents both oversized downloads and layout shift; add priority to the one image above the fold so the browser preloads it. next/font self-hosts fonts and applies size-adjust fallbacks so text does not jump when the font arrives. Give loading skeletons the dimensions of the content they replace.

Tips

  • Set a performance budget (for example, 150 kB First Load JS per route) and fail CI when it is exceeded.
  • Re-run the analyzer after adding any dependency; regressions are cheaper to catch immediately.
  • Test on a throttled connection and a mid-range phone, not only on a fast laptop.
Quick Quiz
Question 1 of 3

Which command opens the bundle treemap after installing `@next/bundle-analyzer`?

Key Takeaways

  • Measure LCP, INP and CLS in the lab with Lighthouse and in the field with useReportWebVitals.
  • @next/bundle-analyzer shows which dependencies inflate client and server bundles.
  • Keep client boundaries at the leaves, avoid barrel imports and lazy load on-demand code.
  • Cache, parallelise and stream on the server; keep the database close to the app.
  • Correctly sized images, self-hosted fonts and sized skeletons eliminate layout shift.

Next lesson: Turbopack, the React Compiler and Build Configuration — understand the toolchain that compiles and optimises your app.