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.
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.
1import Image from "next/image"23// Local image (auto width/height from import)4import heroImage from "@/public/hero.jpg"56export function Hero() {7return (8 <Image9 src={heroImage}10 alt="Hero banner"11 placeholder="blur" // Shows blurred version while loading12 priority // Loads immediately (for above-the-fold images)13 />14)15}1617// Remote image (must specify dimensions)18export function Avatar({ user }) {19return (20 <Image21 src={user.avatarUrl}22 alt={user.name}23 width={48}24 height={48}25 className="rounded-full"26 />27)28}Image rules I follow
⢠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.
1import { Inter, JetBrains_Mono } from "next/font/google"23const inter = Inter({4subsets: ["latin"],5display: "swap",6variable: "--font-inter",7})89const mono = JetBrains_Mono({10subsets: ["latin"],11display: "swap",12variable: "--font-mono",13})1415export 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.
1// Lazy load heavy components2import dynamic from "next/dynamic"34const 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})89// Only loads Chart.js when this component renders10export function Dashboard() {11return (12 <div>13 <h1>Analytics</h1>14 <HeavyChart data={data} />15 </div>16)17}Analyze 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.
Lighthouse Checklist
My 100/100 Lighthouse formula
⢠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