Web Performance Optimization 2026: Core Web Vitals, LCP, CLS, INP Guide
Advertisement
Web Performance 2026: Speed is a Feature
Google uses Core Web Vitals as a ranking signal. A 1-second delay reduces conversions by 7%. Performance is not optional — it's product quality.
- Core Web Vitals: The Three Metrics That Matter
- LCP Optimization: Make the Hero Load Faster
- CLS Prevention: Stop Layout Shifts
- INP Optimization: Fast Interactions
- JavaScript Bundle Optimization
- Image Optimization
- Caching Strategy
- Performance Monitoring
- Performance Checklist
Core Web Vitals: The Three Metrics That Matter
| Metric | What It Measures | Good | Needs Work | Poor |
|---|---|---|---|---|
| LCP | Largest Contentful Paint (loading) | ≤2.5s | ≤4s | >4s |
| INP | Interaction to Next Paint (responsiveness) | ≤200ms | ≤500ms | >500ms |
| CLS | Cumulative Layout Shift (visual stability) | ≤0.1 | ≤0.25 | >0.25 |
INP replaced FID in March 2024 — it measures all interactions, not just the first.
LCP Optimization: Make the Hero Load Faster
The LCP element is usually a hero image, heading, or video thumbnail.
<!-- 1. Preload your LCP image -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
<!-- 2. Use fetchpriority on the actual image -->
<img
src="/hero.webp"
alt="Hero"
fetchpriority="high"
loading="eager"
decoding="async"
width="1200"
height="600"
/>
<!-- 3. DON'T lazy load above-the-fold images -->
<!-- Bad: -->
<img src="/hero.jpg" loading="lazy" />
<!-- 4. Preconnect to image CDN -->
<link rel="preconnect" href="https://cdn.example.com" />
// Next.js: automatic LCP optimization
import Image from 'next/image'
export default function Hero() {
return (
<Image
src="/hero.webp"
alt="Hero image"
width={1200}
height={600}
priority // Marks as LCP, adds preload
sizes="100vw"
/>
)
}
CLS Prevention: Stop Layout Shifts
/* 1. Always set dimensions on images */
img {
width: 100%;
height: auto;
aspect-ratio: 16/9; /* Reserve space before load */
}
/* 2. Reserve space for dynamic content */
.ad-slot {
min-height: 250px; /* Avoid layout shift when ad loads */
}
/* 3. Use CSS transforms instead of layout properties for animations */
/* BAD: causes reflow */
.bad-animation { width: 100px; animation: expand 0.3s; }
@keyframes expand { to { width: 200px; } }
/* GOOD: GPU-accelerated, no layout impact */
.good-animation { transform: scaleX(0.5); animation: expand 0.3s; }
@keyframes expand { to { transform: scaleX(1); } }
// Next.js Font optimization (eliminates font CLS)
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap', // or 'optional' to avoid FOUT
preload: true,
})
export default function Layout({ children }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
)
}
INP Optimization: Fast Interactions
INP measures the time from user input to the next visual update.
// 1. Defer non-critical work
function handleClick(e: React.MouseEvent) {
// Do critical UI update first
setIsLoading(true)
// Defer heavy computation
setTimeout(() => {
processData()
setIsLoading(false)
}, 0)
}
// 2. Use Scheduler API for long tasks
async function processLargeList(items: Item[]) {
const CHUNK_SIZE = 50
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
const chunk = items.slice(i, i + CHUNK_SIZE)
processChunk(chunk)
// Yield to browser between chunks
await new Promise(resolve => setTimeout(resolve, 0))
}
}
// 3. Use Web Workers for CPU-intensive work
const worker = new Worker('/workers/analysis.js')
function analyzeData(data: number[]) {
return new Promise(resolve => {
worker.postMessage(data)
worker.onmessage = (e) => resolve(e.data)
})
}
JavaScript Bundle Optimization
// 1. Dynamic imports for code splitting
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <ChartSkeleton />,
ssr: false, // Don't render on server
})
// 2. Lazy load based on viewport
import { lazy, Suspense } from 'react'
const Comments = lazy(() => import('./Comments'))
function BlogPost() {
return (
<>
<Article />
<Suspense fallback={<CommentsSkeleton />}>
<Comments /> {/* Only loaded when rendered */}
</Suspense>
</>
)
}
// 3. Analyze your bundle
// npm install --save-dev @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// ...your config
})
// Run: ANALYZE=true npm run build
Image Optimization
// Serve modern formats with fallbacks
// <picture> element for WebP/AVIF with JPEG fallback
// In Next.js — automatic WebP/AVIF conversion
<Image
src="/product.jpg"
alt="Product"
width={800}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
quality={75} // Default 75 is usually fine
/>
// For responsive images outside Next.js
function ResponsiveImage({ src, alt }: { src: string; alt: string }) {
return (
<picture>
<source srcSet={`${src}?format=avif`} type="image/avif" />
<source srcSet={`${src}?format=webp`} type="image/webp" />
<img src={src} alt={alt} loading="lazy" decoding="async" />
</picture>
)
}
Caching Strategy
// next.config.js — aggressive caching for static assets
const nextConfig = {
async headers() {
return [
{
source: '/_next/static/(.*)',
headers: [
{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' },
],
},
{
source: '/images/(.*)',
headers: [
{ key: 'Cache-Control', value: 'public, max-age=86400, stale-while-revalidate=604800' },
],
},
{
source: '/api/(.*)',
headers: [
{ key: 'Cache-Control', value: 'no-store' },
],
},
]
},
}
Performance Monitoring
// Report Web Vitals to analytics
import { onLCP, onINP, onCLS, onFCP, onTTFB } from 'web-vitals'
function sendToAnalytics({ name, value, id, rating }: Metric) {
fetch('/api/analytics/vitals', {
method: 'POST',
body: JSON.stringify({ name, value: Math.round(value), id, rating }),
headers: { 'Content-Type': 'application/json' },
keepalive: true, // Survives page unload
})
}
onLCP(sendToAnalytics)
onINP(sendToAnalytics)
onCLS(sendToAnalytics)
onFCP(sendToAnalytics)
onTTFB(sendToAnalytics)
Performance Checklist
| Optimization | LCP | CLS | INP | Difficulty |
|---|---|---|---|---|
| Preload LCP image | ✓ | Easy | ||
Use next/image | ✓ | ✓ | Easy | |
Use next/font | ✓ | Easy | ||
| Code split routes | ✓ | ✓ | Medium | |
| Defer third-party | ✓ | ✓ | ✓ | Medium |
| Web Workers | ✓ | Hard | ||
| Server Components | ✓ | ✓ | Medium |
Start with the easy wins — preload, next/image, next/font. These alone can move you from "needs improvement" to "good" on most sites.
Advertisement