Practical Frontend Tips for Better Core Web Vitals Scores

Getting your Core Web Vitals scores to pass Google’s thresholds doesn’t require a complete infrastructure overhaul. Most performance wins come from smart frontend optimizations that any developer can implement. Here’s how to tackle the most impactful improvements for LCP, INP, and CLS without touching your backend.
Key Takeaways
- Prioritize hero content with fetchpriority and preload hints for better LCP
- Break long JavaScript tasks using scheduler.yield() to improve INP
- Reserve space for all dynamic content to prevent CLS
- Small frontend optimizations can move scores from failing to passing
Optimizing LCP Performance: Make Your Hero Content Lightning Fast
Your Largest Contentful Paint typically involves hero images or above-the-fold text blocks. The key is making these resources discoverable and prioritized from the start.
Smart Image Handling for Better LCP
<!-- Before: Hidden from browser's preload scanner -->
<div class="hero" style="background-image: url('hero.jpg')"></div>
<!-- After: Discoverable and prioritized -->
<img src="hero.webp"
fetchpriority="high"
width="1200"
height="600"
alt="Hero image">
For critical images loaded via JavaScript, add a preload hint:
<link rel="preload" as="image" href="hero.webp" fetchpriority="high">
Resource Prioritization Techniques
Modern browsers support fetchpriority to boost critical resources:
// Deprioritize non-critical images
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
img.fetchPriority = 'low';
});
Remove any loading="lazy"
attributes from LCP images—they delay loading unnecessarily. Also, ensure your LCP resource loads from the initial HTML response, not after JavaScript execution.
Breaking Up Long Tasks for Better INP Performance
INP measures how quickly your page responds to all user interactions, not just the first one. Long JavaScript tasks are the primary culprit behind poor INP scores.
Task Scheduling Patterns
// Before: Blocking the main thread
function processData(items) {
items.forEach(item => {
// Heavy processing
complexCalculation(item);
updateUI(item);
});
}
// After: Yielding to the browser
async function processData(items) {
for (const item of items) {
complexCalculation(item);
updateUI(item);
// Yield control back to the browser
await scheduler.yield();
}
}
For browsers without scheduler.yield() support, use this fallback:
function yieldToMain() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
Optimize Event Handlers
Batch DOM operations and avoid layout thrashing:
// Inefficient: Forces multiple reflows
elements.forEach(el => {
el.style.left = el.offsetLeft + 10 + 'px';
});
// Efficient: Read all, then write all
const positions = elements.map(el => el.offsetLeft);
elements.forEach((el, i) => {
el.style.left = positions[i] + 10 + 'px';
});
Discover how at OpenReplay.com.
Preventing Layout Shifts: Reserve Space and Avoid Reflows
CLS fixes often require the least code but the most discipline. Every dynamic element needs reserved space.
Image and Media Dimensions
<!-- Always specify dimensions -->
<img src="product.jpg" width="400" height="300" alt="Product">
<!-- For responsive images, use aspect-ratio -->
<style>
.responsive-image {
width: 100%;
aspect-ratio: 16/9;
}
</style>
Dynamic Content Patterns
For content that loads after initial render:
/* Reserve minimum space for dynamic content */
.ad-container {
min-height: 250px;
}
.comments-section {
min-height: 400px;
}
/* Skeleton screens prevent shifts */
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
Animation Best Practices
Never animate properties that trigger layout:
/* Causes layout shifts */
.slide-in {
animation: slideIn 0.3s;
}
@keyframes slideIn {
from { margin-left: -100%; }
to { margin-left: 0; }
}
/* No layout shifts */
.slide-in {
animation: slideIn 0.3s;
}
@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
Quick Wins Checklist
For LCP optimization:
- Add
fetchpriority="high"
to hero images - Remove lazy loading from above-the-fold content
- Preload critical fonts and images
- Inline critical CSS
For INP performance:
- Break tasks over 50ms with
scheduler.yield()
- Debounce input handlers
- Move heavy computations to Web Workers
- Use CSS transforms instead of JavaScript animations
For CLS fixes:
- Set explicit dimensions on all images and videos
- Reserve space for dynamic content with min-height
- Use CSS transforms for animations
- Preload web fonts with
font-display: optional
Conclusion
Improving Core Web Vitals doesn’t require architectural changes or expensive infrastructure. Focus on making your hero content discoverable and prioritized, breaking up JavaScript tasks to keep the main thread responsive, and reserving space for every piece of dynamic content. These frontend optimizations alone can move your scores from red to green—and more importantly, deliver the fast, stable experience your users deserve.
FAQs
Frontend optimizations can dramatically improve scores. Most failing sites can reach passing thresholds just by implementing image prioritization, task scheduling, and layout stability fixes. These changes often improve LCP by 30-50%, reduce INP to under 200ms, and eliminate most CLS issues.
Use scheduler.yield() when available as it's specifically designed for task scheduling. For broader browser support, setTimeout with 0ms delay works as a fallback. The key is yielding control back to the browser every 50ms to maintain responsiveness.
Properly sizing and prioritizing your LCP element typically provides the biggest improvement. Adding fetchpriority high to your hero image and removing lazy loading from above-the-fold content can cut LCP time in half on many sites.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.