Core Web Vitals Engineering Playbook: Fixing LCP, INP, and CLS in React & Vite SPAs
Actionable engineering fixes to get Largest Contentful Paint (LCP) under 2.5s, Interaction to Next Paint (INP) under 200ms, and Cumulative Layout Shift (CLS) under 0.1.
Google Search uses Core Web Vitals field data (CrUX) as a page experience ranking signal. Passing the assessment requires hitting the 75th percentile threshold across three metrics: Largest Contentful Paint (LCP ≤ 2.5s), Interaction to Next Paint (INP ≤ 200ms), and Cumulative Layout Shift (CLS ≤ 0.10).
1. Optimizing LCP: Hero Image Byte Weight & Fetch Priority
In over 70% of web pages, the Largest Contentful Paint element is a hero banner image or product photograph. Convert hero PNG/JPG images to WebP using the PNG to WebP Converter and never apply loading="lazy" to above-the-fold images:
<!-- Above-the-fold Hero Image: Eager load + High fetch priority + Explicit dimensions -->
<img
src="/assets/hero-workspace.webp"
width="1200"
height="630"
fetchpriority="high"
decoding="async"
alt="Workspace preview"
/>
2. Eliminating CLS & Reducing INP Main-Thread Blocking
- Reserve Explicit Aspect Ratio Boxes (CLS < 0.1): Always specify
widthandheightattributes on images, embeds, and ad containers so the browser reserves layout space before network payloads arrive. - Yield to the Main Thread on Heavy Input (INP < 200ms): Break long synchronous filter or search calculations into micro-tasks using
React.startTransitionorrequestAnimationFrameso the browser can paint visual feedback immediately after a user keystroke.
Frequently Asked Questions
What replaced First Input Delay (FID) in Google Core Web Vitals?
Interaction to Next Paint (INP) officially replaced FID as a Core Web Vital. Unlike FID, which only measured the input delay of the very first click, INP measures the full latency (input delay + event handler execution + next frame paint) across all user interactions during a page visit.
Why do hero images hurt LCP in React single-page applications?
In a client-rendered SPA, the browser cannot discover an <img> tag inside a lazy-loaded React component until the JavaScript bundle downloads, parses, and executes. Preloading the hero WebP image in index.html and setting fetchpriority="high" fixes this.