← Back to chapters
17advanced

Performance & Optimization

Bundle splitting, image optimization, font loading, Core Web Vitals, and Lighthouse scores.

Performance: Make Your App Fly

A fast website isn't a luxury. It's a requirement. Google ranks faster sites higher, users bounce from slow ones, and nobody waits 5 seconds for a page to load anymore. Here's how to squeeze every millisecond out of Next.js.

Core Web Vitals

Google measures your site's user experience with three metrics. Hit green on all three and you'll rank better.

FeatureWhat it doesWhen to use
LCPLargest Contentful Paint< 2.5s (how fast content appears)
INPInteraction to Next Paint< 200ms (how snappy interactions feel)
CLSCumulative Layout Shift< 0.1 (how stable the layout is)

Image Optimization

Images are usually the biggest performance bottleneck. The next/image component handles this automatically: lazy loading, responsive sizing, modern formats (WebP/AVIF), and no layout shift.

src/components/images.tsx
tsx
1import Image from "next/image"
2
3// Local image (auto width/height from import)
4import heroImage from "@/public/hero.jpg"
5
6export function Hero() {
7return (
8 <Image
9 src={heroImage}
10 alt="Hero banner"
11 placeholder="blur" // Shows blurred version while loading
12 priority // Loads immediately (for above-the-fold images)
13 />
14)
15}
16
17// Remote image (must specify dimensions)
18export function Avatar({ user }) {
19return (
20 <Image
21 src={user.avatarUrl}
22 alt={user.name}
23 width={48}
24 height={48}
25 className="rounded-full"
26 />
27)
28}
šŸš€

Image rules I follow

• Always use next/image, never raw tags
• Add priority to your hero/banner image (first thing users see)
• Use placeholder='blur' for large images
• Set sizes prop for responsive images to avoid serving oversized files
• Configure remotePatterns in next.config.ts for external images

Font Optimization

next/font downloads fonts at build time and self-hosts them. No external requests, no layout shift from font swapping, and automatic subsetting.

src/app/layout.tsx
tsx
1import { Inter, JetBrains_Mono } from "next/font/google"
2
3const inter = Inter({
4subsets: ["latin"],
5display: "swap",
6variable: "--font-inter",
7})
8
9const mono = JetBrains_Mono({
10subsets: ["latin"],
11display: "swap",
12variable: "--font-mono",
13})
14
15export default function RootLayout({ children }) {
16return (
17 <html className={`${inter.variable} ${mono.variable}`}>
18 <body className="font-sans">{children}</body>
19 </html>
20)
21}

Bundle Size

Less JavaScript = faster load. Next.js does code splitting automatically (each route gets its own bundle), but you can make it even better.

src/app/dashboard/page.tsx
tsx
1// Lazy load heavy components
2import dynamic from "next/dynamic"
3
4const HeavyChart = dynamic(() => import("@/components/Chart"), {
5loading: () => <div className="h-64 animate-pulse bg-gray-200" />,
6ssr: false, // Don't render on server (for browser-only libs)
7})
8
9// Only loads Chart.js when this component renders
10export function Dashboard() {
11return (
12 <div>
13 <h1>Analytics</h1>
14 <HeavyChart data={data} />
15 </div>
16)
17}
šŸ’”

Analyze your bundle

Install @next/bundle-analyzer to see exactly what's bloating your bundle:
• npm install @next/bundle-analyzer
• Wrap your next.config.ts with the analyzer
• Run ANALYZE=true npm run build
• A visual treemap opens showing every package and its size

Server Components = Free Performance

This is the single biggest performance win in modern Next.js. Server Components send zero JavaScript to the browser. The component runs on the server, sends HTML, done. Only add 'use client' when you genuinely need interactivity (onClick, useState, useEffect).

Caching Strategies

Next.js caches aggressively by default. Understanding what's cached and when it refreshes is key to both performance and correctness.

FeatureWhat it doesWhen to use
StaticCached at build timeBlog posts, marketing pages
ISRCached + revalidates on timerProduct pages, feeds
DynamicFresh on every requestUser dashboards, search results
use cacheCache any function resultExpensive computations, DB queries

Lighthouse Checklist

šŸš€

My 100/100 Lighthouse formula

• Use Server Components by default (less JS shipped)
• next/image with priority on hero image
• next/font for zero-flash font loading
• Lazy load below-the-fold components with dynamic()
• Avoid layout shift: always set width/height on images
• Minimize third-party scripts (analytics, chat widgets)
• Use Suspense boundaries to stream slow content
• Run 'next build' and check the output sizes

ā–¶Watch and Learn