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.
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+sizesso 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: autofor 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
- Instrument RUM (web-vitals.js → analytics). Track LCP, INP, CLS at p75/p95.
- Find the worst page in the data.
- Profile it — Lighthouse, Performance panel, Coverage.
- Identify the dominant cost — image, JS, render-blocking script, third party.
- Fix the biggest one.
- 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.