Google uses Core Web Vitals as a ranking factor. Sites that load fast, respond quickly, and maintain visual stability rank higher—and provide better user experiences.
This guide explains what Core Web Vitals are, why they matter, and how to optimize each metric for your website.
What Are Core Web Vitals?
Core Web Vitals are three metrics that Google uses to measure user experience:
| Metric | Measures | Good | Needs Improvement | Poor | |--------|----------|------|-------------------|------| | LCP | Loading performance | ≤2.5s | 2.5s - 4.0s | >4.0s | | INP | Interactivity | ≤200ms | 200ms - 500ms | >500ms | | CLS | Visual stability | ≤0.1 | 0.1 - 0.25 | >0.25 |
These metrics replaced the old "page speed" thinking with specific, measurable targets that correlate with real user experience.
LCP: Largest Contentful Paint
LCP measures how long it takes for the largest visible content element to render. This is usually:
- A hero image
- A large text block
- A video thumbnail
What Causes Poor LCP
- Slow server response time - TTFB (Time to First Byte) too high
- Render-blocking resources - CSS and JavaScript blocking the page
- Slow resource load times - Large images, unoptimized fonts
- Client-side rendering - JavaScript building the page instead of server
How to Improve LCP
1. Optimize Images
Images are the most common LCP element. Optimize them aggressively:
// Next.js Image component - automatic optimization
import Image from 'next/image'
<Image
src="/hero-image.jpg"
alt="Hero"
width={1200}
height={600}
priority // Preload LCP image
sizes="(max-width: 768px) 100vw, 1200px"
/>
Key optimizations:
- Use modern formats (WebP, AVIF)
- Serve appropriately sized images (srcset)
- Lazy load below-the-fold images
- Preload LCP images with
priorityprop
2. Preload Critical Resources
Tell the browser what to load first:
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/hero-image.webp" as="image">
3. Reduce Server Response Time
- Use a CDN (Vercel, Cloudflare, etc.)
- Implement caching headers
- Optimize database queries
- Consider edge rendering
4. Eliminate Render-Blocking Resources
// Next.js Script component with strategy
import Script from 'next/script'
<Script
src="https://example.com/script.js"
strategy="afterInteractive" // Load after page is interactive
/>
Script strategies:
beforeInteractive- Load before hydration (critical scripts)afterInteractive- Load after page is interactive (analytics)lazyOnload- Load during idle time (low priority)
5. Use Server-Side Rendering
Client-side rendering delays LCP because JavaScript must execute before content appears:
// Next.js App Router - Server Components by default
// This renders on the server, fast LCP
export default function Page() {
return <h1>Hello World</h1> // Rendered in HTML, not JS
}
Measuring LCP
// Using web-vitals library
import { onLCP } from 'web-vitals'
onLCP((metric) => {
console.log('LCP:', metric.value)
// Send to analytics
})
INP: Interaction to Next Paint
INP replaced FID (First Input Delay) in March 2024. It measures how quickly your site responds to user interactions—clicks, taps, and key presses.
What Causes Poor INP
- Long JavaScript tasks - Tasks blocking the main thread
- Heavy event handlers - Slow click/change handlers
- Large DOM size - Many elements to update
- Hydration delays - React/framework startup time
How to Improve INP
1. Break Up Long Tasks
Long tasks (>50ms) block user interaction:
// Bad: One long task
function processAllItems(items) {
items.forEach(item => heavyProcess(item))
}
// Good: Yield to main thread
async function processAllItems(items) {
for (const item of items) {
heavyProcess(item)
// Yield to allow interactions
await new Promise(resolve => setTimeout(resolve, 0))
}
}
2. Optimize Event Handlers
// Bad: Heavy computation in handler
button.addEventListener('click', () => {
const result = expensiveCalculation() // Blocks UI
updateUI(result)
})
// Good: Defer non-critical work
button.addEventListener('click', () => {
requestAnimationFrame(() => {
const result = expensiveCalculation()
updateUI(result)
})
})
3. Reduce JavaScript Bundle Size
# Analyze your bundle
npm run build
npx @next/bundle-analyzer
- Code split by route
- Dynamic imports for heavy components
- Remove unused dependencies
// Dynamic import - loads only when needed
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <Spinner />,
})
4. Optimize React Rendering
// Memoize expensive components
const ExpensiveList = memo(function ExpensiveList({ items }) {
return items.map(item => <Item key={item.id} {...item} />)
})
// Use useCallback for stable references
const handleClick = useCallback(() => {
doSomething()
}, [])
Measuring INP
import { onINP } from 'web-vitals'
onINP((metric) => {
console.log('INP:', metric.value)
console.log('Interaction target:', metric.attribution?.interactionTarget)
})
CLS: Cumulative Layout Shift
CLS measures visual stability—how much the page layout shifts unexpectedly while loading.
What Causes Poor CLS
- Images without dimensions - Browser doesn't know size until loaded
- Ads and embeds - Dynamic content injected late
- Web fonts - Text reflow when fonts load
- Dynamic content - Content inserted above existing content
How to Improve CLS
1. Always Set Image Dimensions
// Bad: No dimensions, causes layout shift
<img src="/photo.jpg" alt="Photo" />
// Good: Dimensions specified
<img src="/photo.jpg" alt="Photo" width={800} height={600} />
// Best: Next.js Image handles this automatically
<Image src="/photo.jpg" alt="Photo" width={800} height={600} />
2. Reserve Space for Dynamic Content
/* Reserve space for ad container */
.ad-container {
min-height: 250px;
width: 100%;
}
/* Aspect ratio box for videos */
.video-container {
aspect-ratio: 16 / 9;
width: 100%;
}
3. Optimize Font Loading
// Next.js font optimization
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap', // Show fallback immediately
})
Or use font-display in CSS:
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* or 'optional' for less shift */
}
4. Avoid Inserting Content Above Existing Content
// Bad: New content pushes existing content down
const [notifications, setNotifications] = useState([])
// Notification appears at top, shifting everything
<div>
{notifications.map(n => <Notification key={n.id} {...n} />)}
<MainContent />
</div>
// Good: Reserve space or add at bottom
<div>
<MainContent />
{notifications.map(n => <Notification key={n.id} {...n} />)}
</div>
5. Use CSS Transform for Animations
/* Bad: Animating layout properties */
.element {
animation: slide 0.3s;
}
@keyframes slide {
from { margin-left: -100px; }
to { margin-left: 0; }
}
/* Good: Animating transform (no layout shift) */
.element {
animation: slide 0.3s;
}
@keyframes slide {
from { transform: translateX(-100px); }
to { transform: translateX(0); }
}
Measuring CLS
import { onCLS } from 'web-vitals'
onCLS((metric) => {
console.log('CLS:', metric.value)
// Identify which elements shifted
metric.entries.forEach(entry => {
console.log('Shifted element:', entry.sources)
})
})
Tools for Measuring Core Web Vitals
Lab Tools (Synthetic Testing)
| Tool | Best For | |------|----------| | Lighthouse | Quick local audits | | PageSpeed Insights | Production URL analysis | | Chrome DevTools | Detailed debugging | | WebPageTest | Advanced analysis |
Field Data (Real User Monitoring)
| Tool | Best For | |------|----------| | Chrome UX Report (CrUX) | Real-world data from Chrome users | | Search Console | Core Web Vitals report | | Google Analytics 4 | Custom performance tracking |
In Your Code
// web-vitals library
import { onCLS, onLCP, onINP } from 'web-vitals'
function sendToAnalytics(metric) {
// Send to GA4 or your analytics
gtag('event', metric.name, {
value: Math.round(metric.value),
metric_id: metric.id,
metric_value: metric.value,
metric_delta: metric.delta,
})
}
onCLS(sendToAnalytics)
onLCP(sendToAnalytics)
onINP(sendToAnalytics)
Next.js Performance Best Practices
Next.js provides built-in optimizations. Use them:
1. Image Optimization
import Image from 'next/image'
// Automatic: format conversion, sizing, lazy loading
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // For LCP images
/>
2. Font Optimization
import { Inter, Playfair_Display } from 'next/font/google'
const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })
const playfair = Playfair_Display({ subsets: ['latin'], variable: '--font-playfair' })
3. Script Optimization
import Script from 'next/script'
// Analytics - load after page is interactive
<Script
src="https://www.googletagmanager.com/gtm.js"
strategy="afterInteractive"
/>
4. Server Components (App Router)
// Server Component - no JavaScript sent to client
export default async function Page() {
const data = await fetchData()
return <div>{data}</div>
}
// Client Component - only when needed
'use client'
export default function InteractiveWidget() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
Performance vs SEO Connection
Performance directly impacts SEO:
- Ranking factor - Core Web Vitals are part of Google's page experience signals
- Crawl budget - Slow sites get crawled less frequently
- User engagement - Fast sites have lower bounce rates
- Conversion - Speed directly correlates with conversion rates
For more on the SEO connection, see our Technical SEO Fundamentals guide.
Monitoring in Production
Set up monitoring to catch regressions:
- Google Search Console - Core Web Vitals report
- Google Analytics 4 - Track with proper analytics setup
- Real User Monitoring - web-vitals library to your analytics
Quick Wins Checklist
Start with these high-impact, low-effort improvements:
- [ ] Add
priorityto your LCP image - [ ] Set width/height on all images
- [ ] Use Next.js Image component
- [ ] Move analytics to
afterInteractive - [ ] Reserve space for ads/embeds
- [ ] Use
font-display: swap - [ ] Enable Vercel/CDN caching
Related Reading
- Technical SEO Fundamentals - Foundation for rankings
- GA4 & Tag Manager Setup - Measure your vitals
- Structured Data Implementation - Enhanced search results
- Complete Guide to Web Optimization - The full picture
This post is part of the Web Optimization series. Performance is just one piece—combine with SEO, analytics, and structured data for maximum impact.