Performance
Where is the bottleneck — render, network, or bundle?
MEASURE → IDENTIFY → FIX → VERIFY
│ │ │ │
▼ ▼ ▼ ▼
Lighthouse LCP? Image? Score
DevTools TBT? Bundle? improved?
Profiler CLS? Hydrate? No regression?
Never optimize without measuring first. Lighthouse scores, Core Web Vitals, and bundle analysis tell you what's slow. Fix the measured bottleneck, not the assumed one.
Core Web Vitals
| Metric | Target | What It Measures |
|---|---|---|
| LCP | < 2.5s | Largest element render time — usually hero image or heading |
| FID/INP | < 200ms | Input delay — how fast the page responds to interaction |
| CLS | < 0.1 | Layout shift — elements jumping after load |
| FCP | < 1.8s | First paint — when user sees something |
| TBT | < 200ms | Main thread blocked time — JavaScript blocking the UI |
LCP and TBT have the highest weight in Lighthouse scoring.
Optimization Checklist
Images
- All images use
next/imagewith explicitwidthandheight - Format set to WebP/AVIF via built-in loader
- Above-fold images have
priorityprop - Below-fold images lazy load (default behavior)
Bundle
- Heavy components use
next/dynamicwithloadingfallback - No barrel imports pulling entire libraries (
import { x } from 'lib/x'notfrom 'lib') - Tree-shaking verified — check
next buildoutput for page sizes - Third-party scripts loaded with
next/scriptstrategy (lazyOnloadorafterInteractive)
Rendering
- Server Components for data-heavy, non-interactive content
- Client Components only where interactivity required (
onClick,useState,useEffect) - Streaming with
<Suspense>for slow data fetches - Static pages use ISR or SSG — avoid unnecessary SSR
Hydration
- No hydration mismatch warnings in console
- Server and client render identical initial HTML
-
useEffectnot used for data fetching — use RSC or React Query - DOM tree under 1500 nodes per page
Caching
- Full Route Cache for static content
- Data cache for expensive queries (
revalidateorcache()) - Client-side cache via React Query for interactive data
-
generateStaticParams()for dynamic routes with known paths
Profiling
| Tool | What It Measures |
|---|---|
| Lighthouse | Overall performance score, accessibility, SEO |
| Chrome DevTools Performance | Main thread activity, long tasks, layout shifts |
| React Profiler | Component render times, unnecessary re-renders |
next build output | Per-page bundle sizes, static vs dynamic |
| Reassure | Performance regression testing in CI |
Anti-Patterns
| Don't | Do Instead |
|---|---|
Fetch in useEffect | Use RSC async/await or React Query |
| Import entire library | Import specific function from subpath |
| CSS-in-JS at runtime | Tailwind or CSS Modules (zero-runtime) |
| Huge client components | Split into server + thin client wrapper |
Excessive useMemo/useCallback | Only where profiler shows re-render cost |
| Nested render functions | Extract to named components |
Context
- Best Practices — Master pre-deploy checklist
- App Router — Layout, rendering, streaming
- State Management — React Query, Zustand
- UI Components — TailwindCSS, shadcn, headless
- Clean Architecture — Hexagonal patterns