Optimize Core Web Vitals (LCP, INP, CLS) for better page experience and search ranking. Use when asked to "improve Core Web Vitals", "fix LCP", "reduce CLS", "optimize INP", "page experience optimization", or "fix layout shifts". Focuses specifically on the three Core Web Vitals metrics. Do NOT use for general web performance (use perf-web-optimization), Lighthouse audits (use perf-lighthouse), or Astro-specific optimization (use perf-astro).
Optimize Core Web Vitals (LCP, INP, CLS) for better page experience and search ranking. Use when asked to "improve Core Web Vitals", "fix LCP", "reduce CLS", "optimize INP", "page experience optimization", or "fix layout shifts". Focuses specifically on the three Core Web Vitals metrics. Do NOT use for general web performance (use perf-web-optimization), Lighthouse audits (use perf-lighthouse), or Astro-specific optimization (use perf-astro).
license
MIT
metadata
{"author":"web-quality-skills","version":"1.0"}
Core Web Vitals optimization
Targeted optimization for the three Core Web Vitals metrics that affect Google Search ranking and user experience.
The three metrics
Metric
Measures
Good
Needs work
Poor
LCP
Loading
≤ 2.5s
2.5s – 4s
> 4s
INP
Interactivity
≤ 200ms
200ms – 500ms
> 500ms
CLS
Visual Stability
≤ 0.1
0.1 – 0.25
> 0.25
Google measures at the 75th percentile — 75% of page visits must meet "Good" thresholds.
LCP: Largest Contentful Paint
LCP measures when the largest visible content element renders. Usually this is:
<!-- ❌ No hints, discovered late --><imgsrc="/hero.jpg"alt="Hero" /><!-- ✅ Preloaded with high priority --><linkrel="preload"href="/hero.webp"as="image"fetchpriority="high" /><imgsrc="/hero.webp"alt="Hero"fetchpriority="high" />
4. Client-side rendering delays
// ❌ Content loads after JavaScriptuseEffect(() => {
fetch('/api/hero-text')
.then((r) => r.json())
.then(setHeroText)
}, [])
// ✅ Server-side or static rendering// Use SSR, SSG, or streaming to send HTML with contentexportasyncfunctiongetServerSideProps() {
const heroText = awaitfetchHeroText()
return { props: { heroText } }
}
LCP optimization checklist
- [ ] TTFB < 800ms (use CDN, edge caching)
- [ ] LCP image preloaded with fetchpriority="high"
- [ ] LCP image optimized (WebP/AVIF, correct size)
- [ ] Critical CSS inlined (< 14KB)
- [ ] No render-blocking JavaScript in <head>- [ ] Fonts don't block text rendering (font-display: swap)
- [ ] LCP element in initial HTML (not JS-rendered)
INP measures responsiveness across ALL interactions (clicks, taps, key presses) during a page visit. It reports the worst interaction (at 98th percentile for high-traffic pages).
INP breakdown
Total INP = Input Delay + Processing Time + Presentation Delay
Phase
Target
Optimization
Input Delay
< 50ms
Reduce main thread blocking
Processing
< 100ms
Optimize event handlers
Presentation
< 50ms
Minimize rendering work
Common INP issues
1. Long tasks blocking main thread
// ❌ Long synchronous taskfunctionprocessLargeArray(items) {
items.forEach((item) =>expensiveOperation(item))
}
// ✅ Break into chunks with yieldingasyncfunctionprocessLargeArray(items) {
constCHUNK_SIZE = 100for (let i = 0; i < items.length; i += CHUNK_SIZE) {
const chunk = items.slice(i, i + CHUNK_SIZE)
chunk.forEach((item) =>expensiveOperation(item))
// Yield to main threadawaitnewPromise((r) =>setTimeout(r, 0))
// Or use scheduler.yield() when available
}
}
2. Heavy event handlers
// ❌ All work in handler
button.addEventListener('click', () => {
// Heavy computationconst result = calculateComplexThing()
// DOM updatesupdateUI(result)
// AnalyticstrackEvent('click')
})
// ✅ Prioritize visual feedback
button.addEventListener('click', () => {
// Immediate visual feedback
button.classList.add('loading')
// Defer non-critical workrequestAnimationFrame(() => {
const result = calculateComplexThing()
updateUI(result)
})
// Use requestIdleCallback for analyticsrequestIdleCallback(() =>trackEvent('click'))
})
CLS measures unexpected layout shifts. A shift occurs when a visible element changes position between frames without user interaction.
CLS Formula:impact fraction × distance fraction
Common CLS causes
1. Images without dimensions
<!-- ❌ Causes layout shift when loaded --><imgsrc="photo.jpg"alt="Photo" /><!-- ✅ Space reserved --><imgsrc="photo.jpg"alt="Photo"width="800"height="600" /><!-- ✅ Or use aspect-ratio --><imgsrc="photo.jpg"alt="Photo"style="aspect-ratio: 4/3; width: 100%;" />
2. Ads, embeds, and iframes
<!-- ❌ Unknown size until loaded --><iframesrc="https://ad-network.com/ad"></iframe><!-- ✅ Reserve space with min-height --><divstyle="min-height: 250px;"><iframesrc="https://ad-network.com/ad"height="250"></iframe></div><!-- ✅ Or use aspect-ratio container --><divstyle="aspect-ratio: 16/9;"><iframesrc="https://youtube.com/embed/..."style="width: 100%; height: 100%;"></iframe></div>
- [ ] All images have width/height or aspect-ratio
- [ ] All videos/embeds have reserved space
- [ ] Ads have min-height containers
- [ ] Fonts use font-display: optional or matched metrics
- [ ] Dynamic content inserted below viewport
- [ ] Animations use transform/opacity only
- [ ] No content injected above existing content