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.
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.
npm install --save-dev @next/bundle-analyzer// 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.
Most wins come from moving work to the server or deferring it:
"use client" at the leaves; a page that is a Client Component drags every import into the browser.import { Check } from "lucide-react" can pull in thousands of icons; Next.js optimises many popular packages automatically, and you can add others:// next.config.ts
experimental: { optimizePackageImports: ["my-ui-lib"] },next/dynamic, and load third-party scripts with next/script strategies.fetch, URLSearchParams) over utility libraries that reimplement them.fetch options, "use cache", and revalidate windows turn repeated work into static output.Promise.all and stream slow sections inside <Suspense> so TTFB stays low.after() for logging and notifications so they never delay the response.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.
Which command opens the bundle treemap after installing `@next/bundle-analyzer`?
useReportWebVitals.@next/bundle-analyzer shows which dependencies inflate client and server bundles.Next lesson: Turbopack, the React Compiler and Build Configuration — understand the toolchain that compiles and optimises your app.