Menu

17. Performance Optimization

Next.js Master Roadmap - IT Technology

This chapter dives into Core Web Vitals—LCP, CLS, and INP—explaining what they measure, why they matter, and how to optimize them in Next.js applications. You'll learn image and font optimization, lazy loading, dynamic imports, compression, caching, and monitoring strategies to achieve fast, stable, and responsive web experiences.

No MCQ questions available for this chapter.

17. Performance Optimization

Understanding Core Web Vitals

Core Web Vitals are a set of user‑centric metrics that quantify loading performance, visual stability, and interactivity. Improving these scores directly impacts user satisfaction, SEO rankings, and conversion rates.

Largest Contentful Paint (LCP)

LCP measures the time from page start until the largest visible element (usually an image, video, or block‑level text) is rendered. A good LCP is ≤ 2.5 seconds; values between 2.5 s and 4.0 s need improvement; above 4.0 s are poor.

Formula: LCP = time(largest visible element paint) - time(navigation start)

Cumulative Layout Shift (CLS)

CLS quantifies unexpected layout shifts during the page lifecycle. It sums the impact fraction × distance fraction for each shift. A CLS < 0.1 is considered good.

Formula: CLS = Σ (impact fraction × distance fraction)

Interaction to Next Paint (INP)

INP captures the latency of all user interactions (clicks, taps, key presses) and reports the worst observed value. A good INP is ≤ 200 ms.

Formula: INP = max(latency of each interaction)

Image Optimization

Images often dominate LCP. Next.js provides the next/image component to automatically serve responsive, lazily‑loaded images in modern formats.

Basic Usage

import Image from "next/image";

<Image
  src="/hero.jpg"
  alt="Hero banner"
  width={1200}
  height={630}
  priority
/>

The width and height attributes let the browser reserve space, preventing CLS. Setting priority marks the image as critical for LCP.

Remote Images

For external sources, whitelist domains in next.config.js:

// next.config.js
module.exports = {
  images: {
    domains: ["images.unsplash.com", "cdn.example.com"]
  }
};

Responsive Breakpoints

next/image generates multiple sizes using the sizes attribute or default breakpoints, delivering WebP/AVIF when supported.

Font Optimization

External font requests can delay rendering and cause layout shifts. Self‑hosting or using next/font eliminates these issues.

Using next/font with Google Fonts

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

const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });

export default function RootLayout({ children }) {
  return (
    
      {children}
    
  );
}

The generated CSS variable is applied to the root, ensuring fonts are available before first paint, reducing both LCP and CLS.

Lazy Loading

Deferring off‑screen resources reduces initial payload and improves LCP.

Image Lazy Loading (default)

next/image lazy‑loads images that are outside the viewport by default.

Component‑Level Lazy Loading

Use dynamic() with a loading placeholder:

import dynamic from "next/dynamic";
import Spinner from "@/components/Spinner";

const HeavyChart = dynamic(() => import("@/components/HeavyChart"), {
  loading: () => ,
});

This splits the chart’s code into a separate bundle, fetched only when the component enters the viewport.

Dynamic Imports and Code Splitting

Breaking JavaScript into smaller chunks lowers the initial bundle size, directly benefiting INP by reducing main‑thread work.

Route‑Level Splitting (App Router)

Exporting a dynamic constant forces SSR or enables lazy loading:

export const dynamic = "force-dynamic"; // forces SSR, can be combined with lazy

Component‑Level Splitting

const Modal = dynamic(() => import("@/components/Modal"), {
  ssr: false, // client‑only component
});

Marking a component as ssr: false prevents it from being rendered on the server, reducing HTML size and hydration time.

Additional Performance Tactics

  • Compression: Enable next compress (gzip/Brotli) to shrink text assets.
  • HTTP/2 Server Push: Push critical CSS/JS files via Link headers.
  • Cache‑Control Headers: Set long‑lived max‑age for immutable assets and stale‑while‑revalidate for frequently updated resources.
  • Monitoring: Use the web-vitals library or Vercel Analytics to capture real‑user metrics:
import { getCLS, getINP, getLCP } from 'web-vitals';

getCLS(console.log);
getINP(console.log);
getLCP(console.log);

Putting It All Together: Optimization Checklist

  1. Audit LCP with Chrome DevTools → identify the largest element.
  2. Replace unoptimized <img> tags with next/image, adding width, height, and priority where appropriate.
  3. Self‑host fonts or adopt next/font to eliminate external requests.
  4. Apply loading="lazy" to off‑screen images or use dynamic() for heavy components.
  5. Split routes and components via dynamic imports to keep the initial JS bundle under 100 KB gzipped.
  6. Enable compression (gzip/Brotli) and configure HTTP/2 push for critical assets.
  7. Set proper Cache‑Control headers: public, max‑age=31536000, immutable for versioned files.
  8. Monitor Core Web Vitals in production using web-vitals or Vercel Analytics and set performance budgets.

Example: Before & After Optimization

Metric Before After Target
LCP 4.8 s 2.1 s ≤ 2.5 s (good)
CLS 0.25 0.03 < 0.1 (good)
INP 320 ms 140 ms ≤ 200 ms (good)

“Performance is not a feature; it’s the foundation of user experience.” – Web Performance Experts

Conclusion

By mastering image and font optimization, leveraging lazy loading and dynamic imports, applying compression and caching strategies, and continuously measuring Core Web Vitals, you can deliver Next.js applications that load fast, stay visually stable, and respond instantly to user interactions. Implement the checklist above, monitor the results, and iterate to keep performance budgets in check.