Back to Performance
Performance
medium
mid

How do you optimize web performance and reduce page load times?

Optimize in layers: (1) ship less — code split, tree-shake, audit deps; (2) cache more — CDN, HTTP cache, service worker; (3) load smart — preload critical, lazy below-fold, prioritize LCP; (4) format right — AVIF/WebP, Brotli, modern JS; (5) render fast — SSR for first paint, virtualize lists, avoid layout thrash. Measure with real-user metrics (LCP, INP, CLS), fix biggest bottleneck, repeat. Don't micro-optimize what isn't slow.

9 min read·~5 min to think through

Performance optimization is a process: measure → find biggest lever → fix → measure again. Not a one-time checklist.

The big levers (highest ROI first)

1. Ship less code

  • Code split by route and by heavy widget.
  • Tree-shake by importing per-function (not whole namespaces).
  • Audit dependencies quarterly — replace heavy deps (moment → dayjs, lodash → lodash-es per import).
  • Modern JS for modern browsers: type=module + nomodule fallback.
  • Strip dev-only code from production builds.

A typical SaaS can cut initial JS by 30–60% with these alone.

2. Right image formats + sizes

  • AVIF / WebP instead of JPEG/PNG (30–50% smaller).
  • srcset + sizes so mobile doesn't download desktop resolution.
  • Compress aggressively — quality 75–80 is visually indistinguishable from 100.
  • Lazy-load below-fold images with loading="lazy".

Images dominate page weight on most sites. Often the single biggest improvement.

3. Cache + CDN

  • CDN for static assets + ideally HTML (edge SSR / SSG).
  • Long max-age on hashed assets (Cache-Control: public, max-age=31536000, immutable).
  • ETag / If-None-Match for HTML and API.
  • HTTP/2 or HTTP/3 for multiplexing.
  • Brotli compression for text (~20% better than gzip).
  • Service worker for repeat-visit caching + offline.

4. Critical path tuning

  • Preload the LCP image + critical fonts.
  • Inline critical CSS above the fold; async-load the rest.
  • fetchpriority="high" on the LCP image.
  • Defer or async non-critical scripts (analytics, third-party tags).
  • preconnect to third-party origins (https://cdn.example.com).

5. Render performance

  • SSR / SSG for instant first paint (the HTML arrives ready).
  • List virtualization for >1000 rows.
  • Avoid layout thrash — don't interleave reads (offsetWidth) and writes in a loop.
  • CSS transforms over top/left for animations (compositor-only, no layout).
  • content-visibility: auto for offscreen sections (browser skips rendering until needed).

6. JavaScript runtime

  • Debounce / throttle scroll, resize, input.
  • Web workers for CPU-heavy work (parse big JSON, image resize) — keep main thread snappy for INP.
  • Split long tasks (>50ms) — long tasks block INP.
  • Memoize judiciously in React — only what's actually expensive.

7. Data layer

  • React Query / RTKQ / SWR for caching, dedup, refetch.
  • Pagination / cursors — don't fetch 10k rows when 50 will do.
  • GraphQL / sparse fieldsets to avoid over-fetching.
  • Streaming for long responses (SSE for LLM tokens, ReadableStream for big JSON).
  • Optimistic updates so mutations feel instant.

The process

  1. Instrument RUM (web-vitals.js → analytics). Track LCP, INP, CLS at p75/p95.
  2. Find the worst page in the data.
  3. Profile it — Lighthouse, Performance panel, Coverage.
  4. Identify the dominant cost — image, JS, render-blocking script, third party.
  5. Fix the biggest one.
  6. Validate by checking the metric moved on real users (not just in the lab).

Pitfalls

  • Optimizing without measuring — random changes that don't move metrics.
  • Chasing Lighthouse score instead of real user metrics.
  • Blanket memoization in React — useMemo overhead can exceed the saved cost.
  • Ignoring the third-party tax (analytics, ads, chat widgets often eat half the perf budget).
  • Treating CLS as cosmetic — it's a top abandon driver on mobile.
  • Lazy-loading the LCP image (anti-pattern).

Targets to aim for

  • LCP: <2.5s at p75 on mobile.
  • INP: <200ms at p75.
  • CLS: <0.1.
  • Initial JS bundle: <200KB compressed for content sites; <400KB for SaaS dashboards.
  • Total transfer (first visit): <1MB if possible.

Mental model

Performance is a budget across multiple resources (bytes, CPU, network, attention). Spend the budget on what users see first. Defer or drop everything else.

Follow-up questions

  • Which metric do you optimize first if LCP, INP, and CLS are all bad?
  • How do you measure the impact of a perf change?
  • What's the difference between SSR, SSG, and ISR for performance?
  • How do third-party scripts affect performance and how do you contain them?

Common mistakes

  • Optimizing the wrong thing because you didn't measure.
  • Memoizing every value in React — net negative.
  • Forgetting that parse cost on low-end Android is the real bottleneck.
  • Adding service workers without an offline use case — extra complexity, no win.
  • Trusting synthetic scores over real-user data.
  • Letting third-party scripts run uncontrolled on the critical path.

Performance considerations

  • Compounding wins: smaller JS → faster TTI → less main-thread blocking → better INP. Cached HTML → faster TTFB → faster LCP. Preload LCP image → faster LCP. The biggest gains come from removing bytes, not from clever runtime tricks. Measure, fix, validate.

Edge cases

  • Save-Data header and prefers-reduced-data: opt out of aggressive prefetch.
  • Foldable / large viewport devices have different LCP elements — test.
  • Network change mid-session — design for unreliable connectivity.
  • Battery level — back off from polling on low battery if you can.
  • Some optimizations conflict (e.g., aggressive code splitting vs HTTP/2 push budget).

Real-world examples

  • Pinterest, BBC, Vodafone all publicly documented multi-second perf improvements with consistent conversion lifts.
  • Next.js + Vercel + edge SSR setups routinely hit sub-1s LCP on content sites.
  • Wix, Shopify, and other platform vendors invest heavily in image optimization and CDN because perf is product.

Senior engineer discussion

Seniors lead with measurement, not opinions. They tie perf to business outcomes, set budgets, enforce them in CI, and prefer few-but-large fixes (image format, code split) over many-small-ones. They distinguish 'feels slow' (INP) from 'loads slow' (LCP) and pick fixes accordingly. They also know perf is a forever-job — entropy keeps adding bytes — and they bake guardrails (size-limit, perf budgets) into CI.

Related questions