Skip to main content

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

MetricTargetWhat It Measures
LCP< 2.5sLargest element render time — usually hero image or heading
FID/INP< 200msInput delay — how fast the page responds to interaction
CLS< 0.1Layout shift — elements jumping after load
FCP< 1.8sFirst paint — when user sees something
TBT< 200msMain thread blocked time — JavaScript blocking the UI

LCP and TBT have the highest weight in Lighthouse scoring.

Optimization Checklist

Images

  • All images use next/image with explicit width and height
  • Format set to WebP/AVIF via built-in loader
  • Above-fold images have priority prop
  • Below-fold images lazy load (default behavior)

Bundle

  • Heavy components use next/dynamic with loading fallback
  • No barrel imports pulling entire libraries (import { x } from 'lib/x' not from 'lib')
  • Tree-shaking verified — check next build output for page sizes
  • Third-party scripts loaded with next/script strategy (lazyOnload or afterInteractive)

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
  • useEffect not 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 (revalidate or cache())
  • Client-side cache via React Query for interactive data
  • generateStaticParams() for dynamic routes with known paths

Profiling

ToolWhat It Measures
LighthouseOverall performance score, accessibility, SEO
Chrome DevTools PerformanceMain thread activity, long tasks, layout shifts
React ProfilerComponent render times, unnecessary re-renders
next build outputPer-page bundle sizes, static vs dynamic
ReassurePerformance regression testing in CI

Anti-Patterns

Don'tDo Instead
Fetch in useEffectUse RSC async/await or React Query
Import entire libraryImport specific function from subpath
CSS-in-JS at runtimeTailwind or CSS Modules (zero-runtime)
Huge client componentsSplit into server + thin client wrapper
Excessive useMemo/useCallbackOnly where profiler shows re-render cost
Nested render functionsExtract to named components

Context