Web Performance Optimization: Core Web Vitals and Beyond
David Kim

Learn essential techniques for optimizing web performance, improving Core Web Vitals, and delivering exceptional user experiences across all devices.
Web performance is crucial for user experience, SEO rankings, and business success. In this comprehensive guide, we’ll explore modern performance optimization techniques, focusing on Core Web Vitals and practical strategies you can implement today.
Understanding Core Web Vitals
Core Web Vitals are Google’s key metrics for measuring user experience on the web. Let’s break down each metric and how to optimize for them.
Largest Contentful Paint (LCP)
LCP measures loading performance and should occur within 2.5 seconds of when the page first starts loading.
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">src="hero-image-800w.jpg"
"text-[#a6e22e]">srcset="
hero-image-400w.jpg 400w,
hero-image-800w.jpg 800w,
hero-image-1200w.jpg 1200w
"
"text-[#a6e22e]">sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"
"text-[#a6e22e]">alt="Hero image"
"text-[#a6e22e]">loading="eager"
"text-[#a6e22e]">fetchpriority="high"
"text-[#a6e22e]">width="1200"
"text-[#a6e22e]">height="800"
>
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="preload" "text-[#a6e22e]">href="/fonts/inter-var.woff2" "text-[#a6e22e]">as="font" "text-[#a6e22e]">type="font/woff2" crossorigin>
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="preload" "text-[#a6e22e]">href="/critical.css" "text-[#a6e22e]">as="style">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="preload" "text-[#a6e22e]">href="/hero-image-800w.jpg" "text-[#a6e22e]">as="image">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
First Input Delay (FID) / Interaction to Next Paint (INP)
FID measures interactivity, while INP (replacing FID) measures responsiveness throughout the page lifecycle.
"text-[#66d9ef]">class="text-[#75715e] italic">// Optimize JavaScript execution
"text-[#66d9ef]">class="text-[#75715e] italic">// 1. Use code splitting and lazy loading
"text-[#66d9ef]">const LazyComponent = React.lazy(() => "text-[#66d9ef]">import('./LazyComponent'));
"text-[#66d9ef]">class="text-[#75715e] italic">// 2. Debounce expensive operations
"text-[#66d9ef]">function debounce(func, wait) {
"text-[#66d9ef]">let timeout;
"text-[#66d9ef]">return "text-[#66d9ef]">function executedFunction(...args) {
"text-[#66d9ef]">const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
"text-[#66d9ef]">const debouncedSearch = debounce((query) => {
"text-[#66d9ef]">class="text-[#75715e] italic">// Expensive search operation
performSearch(query);
}, 300);
"text-[#66d9ef]">class="text-[#75715e] italic">// 3. Use requestIdleCallback "text-[#66d9ef]">for non-critical tasks
"text-[#66d9ef]">function doNonCriticalWork() {
"text-[#66d9ef]">if ('requestIdleCallback' in window) {
requestIdleCallback((deadline) => {
"text-[#66d9ef]">while (deadline.timeRemaining() > 0 && tasks.length > 0) {
"text-[#66d9ef]">const task = tasks.shift();
task();
}
});
} "text-[#66d9ef]">else {
"text-[#66d9ef]">class="text-[#75715e] italic">// Fallback "text-[#66d9ef]">for browsers without requestIdleCallback
setTimeout(doNonCriticalWork, 1);
}
}
"text-[#66d9ef]">class="text-[#75715e] italic">// 4. Optimize event handlers
"text-[#66d9ef]">const handleScroll = (e) => {
"text-[#66d9ef]">class="text-[#75715e] italic">// Mark as passive "text-[#66d9ef]">for better performance
updateScrollPosition(window.scrollY);
};
window.addEventListener('scroll', handleScroll, { passive: "text-[#ae81ff]">true });
"text-[#66d9ef]">class="text-[#75715e] italic">// 5. Use Web Workers "text-[#66d9ef]">for heavy computations
"text-[#66d9ef]">const worker = "text-[#66d9ef]">new Worker('/heavy-computation-worker.js');
worker.postMessage({ data: largeDataSet });
worker.onmessage = (e) => {
"text-[#66d9ef]">const result = e.data;
updateUI(result);
};
Cumulative Layout Shift (CLS)
CLS measures visual stability and should maintain a score of less than 0.1.
/* Reserve space for dynamic content */
.ad-container {
width: 300px;
height: 250px; /* Reserve space even when ad isn't loaded */
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
}
.loading-skeleton {
width: 100%;
height: 200px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Properly size images */
img {
width: 100%;
height: auto;
aspect-ratio: 16 / 9; /* Prevent layout shift */
}
/* Use CSS transforms for animations */
.animated-element {
transform: translateX(0);
transition: transform 0.3s ease;
}
.animated-element: hover {
transform: translateX(10px); /* Won't cause layout shift */
}
/* Avoid animating layout properties */
/* ❌ Bad - causes layout shifts */
.bad-animation: hover {
width: 200px;
height: 200px;
}
/* ✅ Good - uses transform */
.good-animation: hover {
transform: scale(1.1);
}
Image Optimization Strategies
Images often account for the majority of bytes downloaded on web pages. Here’s how to optimize them effectively.
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">srcset="image.avif"
"text-[#a6e22e]">type="image/avif"
>
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">srcset="image.webp"
"text-[#a6e22e]">type="image/webp"
>
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">src="image.jpg"
"text-[#a6e22e]">alt="Description"
"text-[#a6e22e]">loading="lazy"
"text-[#a6e22e]">width="800"
"text-[#a6e22e]">height="600"
>
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">media="(max-width: 799px)"
"text-[#a6e22e]">srcset="mobile-image.jpg"
>
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">media="(min-width: 800px)"
"text-[#a6e22e]">srcset="desktop-image.jpg"
>
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">src="fallback-image.jpg"
"text-[#a6e22e]">alt="Responsive image"
>
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
Resource Loading Optimization
Optimize how and when resources are loaded to improve performance.
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="dns-prefetch" "text-[#a6e22e]">href="//fonts.googleapis.com">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="dns-prefetch" "text-[#a6e22e]">href="//cdn.example.com">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="preconnect" "text-[#a6e22e]">href="https://fonts.gstatic.com" crossorigin>
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="preload" "text-[#a6e22e]">href="/critical.css" "text-[#a6e22e]">as="style">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="preload" "text-[#a6e22e]">href="/hero-image.jpg" "text-[#a6e22e]">as="image">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="preload" "text-[#a6e22e]">href="/main.js" "text-[#a6e22e]">as="script">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="modulepreload" "text-[#a6e22e]">href="/modules/app.js">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="prefetch" "text-[#a6e22e]">href="/next-page.html">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="prefetch" "text-[#a6e22e]">href="/next-page-styles.css">
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">rel="preload" "text-[#a6e22e]">href="/non-critical.css" "text-[#a6e22e]">as="style" "text-[#a6e22e]">onload="this.">onload=null;this.">rel='stylesheet'">
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">"text-[#a6e22e]">class="text-[#75715e] italic">"text-[#a6e22e]">class="text-[#f92672]">
"text-[#a6e22e]">class="text-[#f92672]">
JavaScript Performance Optimization
"text-[#66d9ef]">class="text-[#75715e] italic">// 1. Bundle optimization with webpack/vite
"text-[#66d9ef]">class="text-[#75715e] italic">// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all',
},
common: {
minChunks: 2,
chunks: 'all',
enforce: "text-[#ae81ff]">true,
},
},
},
},
};
"text-[#66d9ef]">class="text-[#75715e] italic">// 2. Tree shaking with ES modules
"text-[#66d9ef]">class="text-[#75715e] italic">// ✅ Good - only imports what you need
"text-[#66d9ef]">import { debounce } "text-[#66d9ef]">from 'lodash-es';
"text-[#66d9ef]">class="text-[#75715e] italic">// ❌ Bad - imports entire library
"text-[#66d9ef]">import _ "text-[#66d9ef]">from 'lodash';
"text-[#66d9ef]">class="text-[#75715e] italic">// 3. Service Worker "text-[#66d9ef]">for caching
"text-[#66d9ef]">class="text-[#75715e] italic">// sw.js
"text-[#66d9ef]">const CACHE_NAME = 'app-v1';
"text-[#66d9ef]">const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
"text-[#66d9ef]">class="text-[#75715e] italic">// Return cached version or fetch "text-[#66d9ef]">from network
"text-[#66d9ef]">return response || fetch(event.request);
})
);
});
"text-[#66d9ef]">class="text-[#75715e] italic">// 4. Virtualization "text-[#66d9ef]">for large lists
"text-[#66d9ef]">class VirtualList {
constructor(container, items, itemHeight) {
this.container = container;
this.items = items;
this.itemHeight = itemHeight;
this.containerHeight = container.clientHeight;
this.visibleItems = Math.ceil(this.containerHeight / itemHeight);
this.scrollTop = 0;
this.render();
this.bindEvents();
}
render() {
"text-[#66d9ef]">const startIndex = Math.floor(this.scrollTop / this.itemHeight);
"text-[#66d9ef]">const endIndex = Math.min(startIndex + this.visibleItems, this.items.length);
"text-[#66d9ef]">const visibleItems = this.items.slice(startIndex, endIndex);
this.container.innerHTML = '';
this.container.style.height = `${this.items.length * this.itemHeight}px`;
visibleItems.forEach((item, index) => {
"text-[#66d9ef]">const element = document.createElement('div');
element.style.position = 'absolute';
element.style.top = `${(startIndex + index) * this.itemHeight}px`;
element.style.height = `${this.itemHeight}px`;
element.textContent = item;
this.container.appendChild(element);
});
}
bindEvents() {
this.container.addEventListener('scroll', () => {
this.scrollTop = this.container.scrollTop;
this.render();
});
}
}
"text-[#66d9ef]">class="text-[#75715e] italic">// 5. Web Workers "text-[#66d9ef]">for heavy computation
"text-[#66d9ef]">class="text-[#75715e] italic">// main.js
"text-[#66d9ef]">const worker = "text-[#66d9ef]">new Worker('/fibonacci-worker.js');
worker.postMessage({ n: 40 });
worker.onmessage = (e) => {
console.log('Fibonacci result:', e.data);
};
"text-[#66d9ef]">class="text-[#75715e] italic">// fibonacci-worker.js
self.onmessage = "text-[#66d9ef]">function(e) {
"text-[#66d9ef]">const n = e.data.n;
"text-[#66d9ef]">const result = fibonacci(n);
self.postMessage(result);
};
"text-[#66d9ef]">function fibonacci(n) {
"text-[#66d9ef]">if (n < 2) "text-[#66d9ef]">return n;
"text-[#66d9ef]">return fibonacci(n - 1) + fibonacci(n - 2);
}
CSS Performance Optimization
/* 1. Critical CSS extraction */
/* Above-the-fold critical styles */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.hero {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
/* 2. Optimize CSS selectors */
/* ❌ Slow - complex selectors */
.sidebar ul li a: hover span.icon {
color: red;
}
/* ✅ Fast - simple class-based selectors */
.nav-link-icon: hover {
color: red;
}
/* 3. Use CSS containment */
.independent-widget {
contain: layout style paint;
}
.article-content {
contain: layout;
}
/* 4. Optimize animations */
/* ✅ Good - uses transform and opacity */
.smooth-animation {
transform: translateX(0);
opacity: 1;
transition: transform 0.3s ease, opacity 0.3s ease;
will-change: transform, opacity;
}
.smooth-animation: hover {
transform: translateX(10px);
opacity: 0.8;
}
/* 5. Use CSS Grid and Flexbox efficiently */
.efficient-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
/* 6. Font loading optimization */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* Show fallback font while loading */
unicode-range: U+0000-00FF; /* Load only needed characters */
}
/* 7. Reduce unused CSS with PurgeCSS */
/* Configure build tools to remove unused styles */
Performance Monitoring and Testing
"text-[#66d9ef]">class="text-[#75715e] italic">// 1. Core Web Vitals monitoring
"text-[#66d9ef]">import { getLCP, getFID, getCLS } "text-[#66d9ef]">from 'web-vitals';
"text-[#66d9ef]">function sendToAnalytics(metric) {
"text-[#66d9ef]">class="text-[#75715e] italic">// Send to your analytics service
gtag('event', metric.name, {
event_category: 'Web Vitals',
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
event_label: metric.id,
non_interaction: "text-[#ae81ff]">true,
});
}
getLCP(sendToAnalytics);
getFID(sendToAnalytics);
getCLS(sendToAnalytics);
"text-[#66d9ef]">class="text-[#75715e] italic">// 2. Performance Observer API
"text-[#66d9ef]">const observer = "text-[#66d9ef]">new PerformanceObserver((list) => {
"text-[#66d9ef]">for ("text-[#66d9ef]">const entry of list.getEntries()) {
"text-[#66d9ef]">if (entry.entryType === 'navigation') {
console.log('Navigation timing:', {
dns: entry.domainLookupEnd - entry.domainLookupStart,
connection: entry.connectEnd - entry.connectStart,
request: entry.responseStart - entry.requestStart,
response: entry.responseEnd - entry.responseStart,
dom: entry.domContentLoadedEventEnd - entry.responseEnd,
load: entry.loadEventEnd - entry.loadEventStart,
});
}
}
});
observer.observe({ entryTypes: ['navigation', 'paint', 'largest-contentful-paint'] });
"text-[#66d9ef]">class="text-[#75715e] italic">// 3. Resource timing analysis
"text-[#66d9ef]">function analyzeResourceTiming() {
"text-[#66d9ef]">const resources = performance.getEntriesByType('resource');
"text-[#66d9ef]">const slowResources = resources
.filter(resource => resource.duration > 1000)
.sort((a, b) => b.duration - a.duration);
console.log('Slow resources:', slowResources);
"text-[#66d9ef]">const largeResources = resources
.filter(resource => resource.transferSize > 100000)
.sort((a, b) => b.transferSize - a.transferSize);
console.log('Large resources:', largeResources);
}
"text-[#66d9ef]">class="text-[#75715e] italic">// 4. User-centric performance metrics
"text-[#66d9ef]">function measureUserExperience() {
"text-[#66d9ef]">class="text-[#75715e] italic">// Time to Interactive (TTI)
"text-[#66d9ef]">const observer = "text-[#66d9ef]">new PerformanceObserver((list) => {
"text-[#66d9ef]">for ("text-[#66d9ef]">const entry of list.getEntries()) {
"text-[#66d9ef]">if (entry.name === 'first-contentful-paint') {
console.log('FCP:', entry.startTime);
}
}
});
observer.observe({ entryTypes: ['paint'] });
"text-[#66d9ef]">class="text-[#75715e] italic">// Custom business metrics
performance.mark('feature-start');
"text-[#66d9ef]">class="text-[#75715e] italic">// ... feature initialization
performance.mark('feature-end');
performance.measure('feature-init', 'feature-start', 'feature-end');
}
"text-[#66d9ef]">class="text-[#75715e] italic">// 5. Automated performance testing
"text-[#66d9ef]">class="text-[#75715e] italic">// Using Puppeteer and Lighthouse
"text-[#66d9ef]">const puppeteer = require('puppeteer');
"text-[#66d9ef]">const lighthouse = require('lighthouse');
"text-[#66d9ef]">async "text-[#66d9ef]">function auditPerformance(url) {
"text-[#66d9ef]">const browser = "text-[#66d9ef]">await puppeteer.launch();
"text-[#66d9ef]">const { lhr } = "text-[#66d9ef]">await lighthouse(url, {
port: "text-[#66d9ef]">new URL(browser.wsEndpoint()).port,
output: 'json',
logLevel: 'info',
});
console.log('Performance score:', lhr.categories.performance.score * 100);
console.log('FCP:', lhr.audits['first-contentful-paint'].displayValue);
console.log('LCP:', lhr.audits['largest-contentful-paint'].displayValue);
"text-[#66d9ef]">await browser.close();
}
Performance Budget and CI/CD Integration
// performance-budget.json
{
"budget": [
{
"resourceSizes": [
{
"resourceType": "script",
"budget": 400
},
{
"resourceType": "total",
"budget": 2000
}
],
"resourceCounts": [
{
"resourceType": "third-party",
"budget": 10
}
]
}
]
}
// webpack-bundle-analyzer configuration
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: process.env.CI ? 'static' : 'server',
openAnalyzer: !process.env.CI,
})
]
};
// GitHub Actions workflow
name: Performance Check
on: [push, pull_request]
jobs: lighthouse: runs-on: ubuntu-latest
steps: - uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with: node-version: '16'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Lighthouse CI
run: |
npm install -g @lhci/cli@0.8.x
lhci autorun
env: LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
Conclusion
Web performance optimization is an ongoing process that requires attention to multiple aspects:
- Core Web Vitals provide measurable targets for user experience
- Image optimization can dramatically reduce page load times
- Resource loading strategies ensure critical content loads first
- JavaScript optimization prevents blocking the main thread
- CSS optimization reduces render-blocking resources
- Performance monitoring helps maintain optimal performance over time
The key is to establish a performance budget, implement monitoring from day one, and make performance a part of your development workflow. Remember that performance is not just about fast loading times – it’s about creating smooth, responsive experiences that delight your users.
Start with the biggest impact optimizations first: optimize images, eliminate render-blocking resources, and implement proper resource loading strategies. Then progressively enhance with advanced techniques as your application grows in complexity.