Optimizing Web Performance: Core Web Vitals and Beyond
Optimizing Web Performance: Core Web Vitals and Beyond
Website performance directly impacts user experience, conversion rates, and SEO rankings. Let's explore practical techniques to optimize your web applications for peak performance.
Understanding Core Web Vitals
Google's Core Web Vitals measure real-world user experience:
Largest Contentful Paint (LCP)
Measures loading performance. LCP should occur within 2.5 seconds.
// Monitor LCP
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.startTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
First Input Delay (FID) / Interaction to Next Paint (INP)
Measures interactivity. FID should be less than 100 milliseconds.
// Monitor FID
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('FID:', entry.processingStart - entry.startTime);
}
}).observe({ entryTypes: ['first-input'] });
Cumulative Layout Shift (CLS)
Measures visual stability. CLS should be less than 0.1.
// Monitor CLS
let clsScore = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsScore += entry.value;
}
}
console.log('CLS:', clsScore);
}).observe({ entryTypes: ['layout-shift'] });
Image Optimization
Images often account for most of a page's weight:
Next.js Image Component
import Image from 'next/image';
function Hero() {
return (
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority // Load immediately for LCP
placeholder="blur"
blurDataURL={blurDataUrl}
/>
);
}
Responsive Images
<picture>
<source
media="(max-width: 640px)"
srcset="hero-mobile.webp"
type="image/webp"
/>
<source
media="(max-width: 1024px)"
srcset="hero-tablet.webp"
type="image/webp"
/>
<img
src="hero-desktop.jpg"
alt="Hero"
loading="lazy"
decoding="async"
/>
</picture>
Image Optimization Checklist
- ✅ Use modern formats (WebP, AVIF)
- ✅ Implement responsive images
- ✅ Lazy load below-the-fold images
- ✅ Add explicit width/height
- ✅ Use CDN with image optimization
JavaScript Optimization
Reduce JavaScript impact on performance:
Code Splitting
// Dynamic imports
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <LoadingSpinner />,
ssr: false
});
// Route-based splitting (automatic in Next.js)
// pages/about.js only loads when visiting /about
Tree Shaking
// Bad - imports entire library
import _ from 'lodash';
const result = _.debounce(fn, 300);
// Good - imports only what's needed
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
Bundle Analysis
# Next.js bundle analyzer
npm install @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({});
CSS Optimization
Optimize CSS delivery and rendering:
Critical CSS
// Extract and inline critical CSS
import { getCssText } from '@stitches/react';
export default function Document() {
return (
<Html>
<Head>
<style
id="stitches"
dangerouslySetInnerHTML={{ __html: getCssText() }}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
CSS Containment
/* Improve rendering performance */
.card {
contain: layout style paint;
}
.sidebar {
contain: strict; /* All containment types */
}
Loading Strategies
Implement smart loading patterns:
Lazy Loading Components
import { lazy, Suspense } from 'react';
const Comments = lazy(() => import('./Comments'));
function BlogPost() {
return (
<article>
<h1>My Blog Post</h1>
<p>Content...</p>
<Suspense fallback={<div>Loading comments...</div>}>
<Comments />
</Suspense>
</article>
);
}
Intersection Observer
// Lazy load when element enters viewport
function useLazyLoad(ref, callback) {
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
callback();
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, [ref, callback]);
}
Resource Hints
<!-- DNS prefetch for external domains -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com" />
<!-- Preconnect for critical resources -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Preload critical resources -->
<link
rel="preload"
href="/fonts/inter.woff2"
as="font"
type="font/woff2"
crossorigin
/>
Font Optimization
Optimize web fonts for better performance:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap; /* Show fallback immediately */
unicode-range: U+0000-00FF; /* Latin characters only */
}
/* Preload critical characters */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-subset.woff2') format('woff2');
font-display: block; /* Block for critical text */
unicode-range: U+0041-005A; /* A-Z only */
}
Performance Monitoring
Implement monitoring to track improvements:
// Custom performance monitoring
class PerformanceMonitor {
constructor() {
this.metrics = {};
}
measure(name, fn) {
const start = performance.now();
const result = fn();
const duration = performance.now() - start;
this.metrics[name] = duration;
// Send to analytics
if (window.gtag) {
gtag('event', 'timing_complete', {
name,
value: Math.round(duration)
});
}
return result;
}
reportWebVitals() {
if ('web-vitals' in window) {
const { getCLS, getFID, getLCP } = window['web-vitals'];
getCLS(this.sendToAnalytics);
getFID(this.sendToAnalytics);
getLCP(this.sendToAnalytics);
}
}
sendToAnalytics({ name, value }) {
// Send to your analytics service
console.log(`${name}: ${value}`);
}
}
Server-Side Optimizations
Caching Headers
// Next.js API route with caching
export default function handler(req, res) {
res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
);
res.json({ data: 'cached response' });
}
Compression
// Enable compression in Next.js
// next.config.js
module.exports = {
compress: true,
// Custom headers
async headers() {
return [
{
source: '/:all*(js|css|jpg|png)',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
];
},
};
Performance Checklist
- [ ] Images: WebP/AVIF, lazy loading, proper sizing
- [ ] JavaScript: Code splitting, tree shaking, minimal bundles
- [ ] CSS: Critical CSS, unused CSS removal
- [ ] Fonts: Subset, preload, font-display
- [ ] Caching: Proper headers, CDN usage
- [ ] Monitoring: RUM, synthetic monitoring
- [ ] SEO: Meta tags, structured data, sitemap
Tools for Performance Testing
- Lighthouse: Built into Chrome DevTools
- WebPageTest: Detailed performance analysis
- PageSpeed Insights: Real-world data from Chrome users
- Chrome DevTools: Performance profiling
- Bundle Analyzer: Visualize JavaScript bundles
Conclusion
Web performance optimization is an ongoing process. Focus on Core Web Vitals as a baseline, but don't stop there. Implement comprehensive monitoring, iterate based on real user data, and always test performance impacts before deploying changes.
Remember: every millisecond counts. A faster website means happier users, better conversions, and improved search rankings. Start optimizing today!