How do you fix poor Core Web Vitals with profiling & fixes?
Front-End Developer
answer
An effective Core Web Vitals fix starts with a rigorous profiling plan: capture field data, then run Lighthouse/DevTools to map time to bytes. Read the network waterfall to trim blocking scripts and fonts; profile main-thread work to cut render-blocking JS; measure hydration cost and defer non-critical islands. Concrete fixes: script strategy (defer/module), selective preloading, tuned image pipeline, and React concurrency to unblock input.
Long Answer
Poor Core Web Vitals are symptoms. The cure is a ranked, testable plan: get field data, profile the page life-cycle, and ship fixes that remove real user delay. Here is a pragmatic workflow with concrete remedies.
1) Ground truth
Use field telemetry (CrUX/RUM). Slice by template, device, network, region. Identify the failing metric—LCP, INP, or CLS—and where it fails.
2) Reproducible lab
In DevTools, emulate a mid-range phone, set 4× CPU throttle and Slow 4G, then record a Performance trace from navigation to idle plus an interaction trace for INP.
3) Waterfall as budget
Inspect the network waterfall. Flag render-blocking CSS/JS, long TTFB, repeats, needless preloads, and extra handshakes. Verify priorities, HTTP/2/3, brotli, and caching.
4) Main-thread diet
Open the flame chart. Tally parse/compile/execute JS, style, layout, paint. Highlight long tasks (>50 ms) and handlers that block input (INP). Locate hydration spans after first paint.
5) Hydration & concurrency
If hydration is heavy, split into islands and hydrate only interactive regions. Consider partial/progressive hydration or React Server Components. Turn on React 18 concurrency (startTransition, useDeferredValue, Suspense) and measure INP before/after.
6) Network fixes
Shrink HTML; inline only critical CSS; defer the rest. Use preconnect to key origins. Preload exactly one asset that shifts the path (LCP image or key font). Upgrade to HTTP/2/3, enable brotli, and cache versioned assets.
7) Script strategy
Default to type=module + defer. Audit third-party tags with Coverage/request-blocking; delete or sandbox offenders. Code-split routes and above-the-fold components; lazy-load the rest. Prefer native APIs and smaller libs; tree-shake.
8) Image pipeline
Serve responsive images (srcset/sizes) in AVIF/WebP with decode hints. Lazy-load below-the-fold. Preload only the LCP image with as=image + imagesrcset. Give LCP a fixed size or aspect-ratio to avoid CLS.
9) Fonts
Subset/self-host; font-display: swap or optional. Preload one critical face if it helps LCP. Use size-adjust so fallback metrics match.
10) Server/data
Lower TTFB with edge caching and request coalescing. Stream HTML; compress JSON; paginate; avoid shipping data you immediately filter. For RSC, stream template HTML early and defer client bundles.
11) Guardrails
Set budgets (HTML ≤25 KB, JS ≤170 KB, CSS ≤35 KB, LCP image ≤100 KB). Automate Lighthouse CI/SpeedCurve on realistic profiles. Fail builds on regression; keep rollback ready. Track p75 LCP/INP/CLS by template.
12) Triage order
Ship smallest wins first: remove sync scripts, inline critical CSS, size/preload LCP, cache static assets. Next cut JS. Finally adopt islands/RSC/concurrency where justified. Validate each change in traces and field data.
Profile like a detective; optimize like a surgeon. Targeted waterfall cuts, main-thread dieting, hydration control, and React concurrency turn poor Web Vitals into durable, user-visible gains.
Table
Common Mistakes
Treating Lighthouse as the truth and ignoring field data, so fixes target the lab device not real users. Preloading everything: fonts, images, chunks—turning the critical path into a traffic jam. Animating layout properties and forcing sync measures (reflow thrash) right before LCP. Shipping megabytes of JavaScript, then trying to mask it with defer without deleting any code. Hydrating entire pages when only a few islands are interactive. Letting third-party tags run at high priority, block input, and tank INP. Over-eager will-change and layer promotion, which burns memory and janks scroll. Optimizing images but forgetting explicit dimensions, causing CLS. Skipping budgets and CI, so regressions creep back. Neglecting server TTFB—slow origins make every frontend trick moot. Relying on generic CDNs without cache-busting, causing stale assets and inconsistent measurements.
Sample Answers (Junior / Mid / Senior)
Junior:
“I’d check field data to see whether LCP or INP is failing, then run a DevTools trace on mobile emulation. I’d inline critical CSS, change scripts to defer, lazy-load images, and preload only the hero image. I’d trim big libraries and confirm improvements with a new trace.”
Mid:
“I segment CrUX by template, profile waterfall and main thread, and target worst long tasks. I split bundles by route, swap heavy deps, and move non-critical work behind requestIdleCallback. For hydration, I convert static sections to server HTML and hydrate only interactive islands. I set budgets and add Lighthouse CI to block regressions.”
Senior:
“I pair RUM with lab traces to rank work by user impact. Network: preconnect key origins, HTTP/3/brotli, and targeted preloads. JavaScript: module+defer, code deletion, and route splitting. React: islands, React 18 concurrency (startTransition, useDeferredValue, Suspense), or RSC. Images/fonts: responsive AVIF/WebP, LCP preload, subset/self-host fonts with size-adjust. Merges gate on p75 LCP/INP/CLS with rollback on regression.”
Evaluation Criteria
Strong answers show a repeatable profiling plan tied to field data, not just Lighthouse scores. They describe how to read the network waterfall (blockers, TTFB, priorities), quantify main-thread time (parse/execute, style/layout/paint, long tasks), and identify hydration spans. They propose concrete fixes: script strategy (type=module + defer, code deletion, route splitting), precise preloads/preconnect, responsive image pipeline, and font hygiene. They understand React concurrency (startTransition, useDeferredValue, Suspense) and when to adopt islands or RSC. They commit to budgets, CI gates, and rollback, and validate with before/after traces plus p75 LCP/INP/CLS in RUM. Interviewers also want risk staging (smallest wins first), realistic device/network profiles, and guardrails like query cost/size limits. Bonus for product trade-offs, per-route budgets, and keeping gains durable via dashboards and ownership.
Preparation Tips
Create a repeatable lab. In Chrome, emulate a mid-range phone (4× CPU) and Slow 4G. Record a navigation trace and an interaction trace. Practice reading the waterfall: find blockers, bad priorities, long TTFB, duplicate downloads. Practice the flame chart: locate long tasks, JS parse/execute, style/layout cycles, and hydration spans. Build a checklist of fixes you can deploy in under a day: switch to module+defer, inline critical CSS, size/preload the LCP image, add preconnect to APIs/fonts, and delete one heavy dependency. Optimize the image pipeline (AVIF/WebP, srcset/sizes, lazy-load) and fonts (subset, font-display, size-adjust). Enable React 18 concurrency on a demo app and verify lower INP. Set budgets and wire Lighthouse CI or SpeedCurve to fail on regression. Finally, prepare a 60–90 s narrative that ties field data to traces and shows before/after metrics for LCP/INP/CLS, including one rollback story. Include a template for sharing wins: screenshot of traces, bytes removed, and p75 improvements per template, plus owners and next steps.
Real-world Context
A retail homepage failed LCP and INP on mobile. Traces showed a blocking font, an oversized hero, and a 230 ms analytics tag on the main thread. Fixes: preconnect font CDN, subset/self-host one face with size-adjust, AVIF hero with explicit size and preload, and move analytics to defer + idle. HTML shrank 9 KB, JS −120 KB, LCP image −180 KB. p75 LCP dropped from 3.6 s → 2.2 s; INP from 280 ms → 140 ms.
A dashboard app missed INP. Flame chart revealed long hydration after paint and chatty third-party widgets. We adopted islands for non-interactive sections, enabled React 18 startTransition and useDeferredValue, and split routes. Widgets were sandboxed and loaded async. INP improved 45%, and input delay spikes vanished.
A content site’s CLS came from webfont swaps and unsized images. Adding width/height, aspect-ratio, and font-display: optional stabilized layout; repeat-view LCP improved with long cache + versioned assets. A follow-up budget and CI gate kept regressions out; two later PRs that reintroduced sync scripts were auto-blocked until fixed.
Key Takeaways
- Start from field data; reproduce in a realistic lab.
- Cut blockers in the network waterfall before touching code structure.
- Put the main thread on a diet; delete JS before you defer it.
- Hydrate only what’s interactive; use React concurrency or islands/RSC.
- Guard wins with budgets, CI gates, and a rollback plan.
Practice Exercise
Scenario: Your product landing fails p75 LCP and INP on mobile. You must profile and ship improvements in 72 hours without redesign.
Tasks:
- Field data: export RUM by template; confirm which pages fail and on which device/network segments. Set targets (LCP ≤ 2.5 s, INP ≤ 200 ms).
- Lab: record navigation + interaction traces on a mid-range profile (4× CPU, Slow 4G). Annotate blockers, long tasks, hydration spans.
- Waterfall fixes: inline critical CSS; change scripts to module+defer; add preconnect for fonts/API; remove one preload; cache versioned assets.
- Main-thread fixes: delete or swap one heavy dependency; split route bundle; memoize hot components; batch DOM; move non-critical work to idle.
- Hydration fixes: convert two static sections to islands; add React 18 startTransition around expensive updates; use Suspense to stream non-critical data.
- Images/fonts: convert hero to AVIF with explicit size and preload; apply srcset/sizes to gallery; subset/self-host one font with font-display: swap and size-adjust.
- Guardrails: add budgets (HTML/JS/CSS/LCP image); wire Lighthouse CI to fail on regression; prepare rollback.
- Report: rerun traces; publish a one-page summary with bytes removed, new p75 LCP/INP/CLS, screenshots, and next steps. Include owners and due dates for follow-ups (e.g., migrate two widgets to islands, retire legacy tag), and attach the failing PRs blocked by budgets as proof the guardrails work.

