Performance isn't a luxury—it's essential. Slow sites lose users. Here's how to make yours fast.
Measuring Performance
Core Web Vitals
Google's user-centric performance metrics.
- LCP (Largest Contentful Paint): < 2.5s
- FID (First Input Delay): < 100ms
- CLS (Cumulative Layout Shift): < 0.1
Performance API
Measure real-world performance.
// Measure LCP
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.startTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
// Measure FID
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('FID:', entry.processingStart - entry.startTime);
}
}).observe({ entryTypes: ['first-input'] });
// Measure CLS
let clsValue = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
console.log('CLS:', clsValue);
}
}
}).observe({ entryTypes: ['layout-shift'] });Network Optimization
Reduce HTTP Requests
Bundle resources, combine files when beneficial.
// Bad: Multiple requests
import _ from 'lodash';
import debounce from 'lodash/debounce';
// Good: Single request
import { debounce } from 'lodash';HTTP/2 and HTTP/3
Leverage multiplexing and server push.
# Enable HTTP/2
listen 443 ssl http2;
# Enable HTTP/3 (QUIC)
listen 443 quic;CDN Usage
Deliver content from edge locations.
<!-- Use CDN for libraries -->
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>Preconnect and DNS-Prefetch
Warm up connections to important origins.
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://static.example.com">Resource Loading
Lazy Loading
Defer loading of below-the-fold content.
<!-- Native lazy loading for images -->
<img src="image.jpg" loading="lazy" alt="...">
<!-- Lazy load iframes -->
<iframe src="video.html" loading="lazy"></iframe>Code Splitting
Split code into smaller chunks.
// Route-based splitting
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
// Component-based splitting
const HeavyComponent = lazy(() => import('./HeavyComponent'));Dynamic Imports
Load modules on demand.
button.addEventListener('click', async () => {
const module = await import('./heavy-module.js');
module.doSomething();
});Asset Optimization
Image Optimization
Serve appropriately sized, compressed images.
<!-- Responsive images with srcset -->
<img
src="image-800.jpg"
srcset="image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
loading="lazy"
alt="..."
>
<!-- Modern formats with fallback -->
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.avif" type="image/avif">
<img src="image.jpg" alt="...">
</picture>Font Optimization
Use font-display for faster rendering.
@font-face {
font-family: 'Custom Font';
src: url('font.woff2') format('woff2');
font-display: swap; /* Prevent FOIT */
unicode-range: U+0020-007E; /* Subset characters */
}Compression
Enable Brotli or Gzip compression.
# Brotli compression
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;
# Brotli (better compression)
brotli on;
brotli_types text/plain text/css application/json application/javascript;Caching Strategies
Cache Headers
Set appropriate cache policies.
# Static assets - long cache
location /static/ {
expires: 1y;
add_header Cache-Control: "public, immutable";
}
# HTML - short cache
location / {
expires: 1h;
add_header Cache-Control: "public";
}Service Workers
Cache assets for offline use.
// Install event - cache assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll([
'/',
'/styles.css',
'/app.js',
]);
})
);
});
// Fetch event - serve from cache
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});Rendering Optimization
Reduce Layout Thrashing
Batch DOM reads and writes.
// Bad - causes layout thrashing
element1.height = 100;
const height1 = element2.offsetHeight;
element1.height = height1;
// Good - batch operations
const height1 = element2.offsetHeight;
const height2 = element3.offsetHeight;
element1.height = height1;
element2.height = height2;Virtual Scrolling
Render only visible items.
import { FixedSizeList } from 'react-window';
<FixedSizeList
height={600}
itemCount={10000}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>Item {index}</div>
)}
</FixedSizeList>Debouncing and Throttling
Limit expensive operations.
// Debounce - wait until user stops
const debouncedSearch = debounce((query) => {
searchAPI(query);
}, 300);
// Throttle - limit execution frequency
const throttledScroll = throttle(() => {
updatePosition();
}, 100);JavaScript Optimization
Tree Shaking
Remove unused code.
// Bad: Import entire library
import _ from 'lodash';
// Good: Import what you need
import { debounce } from 'lodash-es';Minification
Use production builds.
// Development
const greeting = "Hello, " + name + "!";
// Production (minified)
const g="Hello, "+name+"!";Avoid Memory Leaks
Clean up subscriptions and listeners.
useEffect(() => {
const subscription = observable.subscribe(data => {
setData(data);
});
return () => {
subscription.unsubscribe(); // Cleanup
};
}, []);CSS Optimization
Critical CSS
Inline above-the-fold styles.
<style>
/* Critical CSS for immediate render */
.hero { background: blue; color: white; }
</style>
<link rel="stylesheet" href="styles.css">Reduce Selector Complexity
Simplify selectors.
/* Bad - complex selector */
nav ul li a:hover span.icon {}
/* Good - simple selector */
.nav-icon:hover {}Contain Property
Isolate layout recalculations.
.sidebar {
contain: layout style paint;
}Monitoring
Real User Monitoring (RUM)
Track actual user performance.
// Send performance data to analytics
const perfData = performance.getEntriesByType('navigation')[0];
const metrics = {
loadTime: perfData.loadEventEnd - perfData.fetchStart,
domContentLoaded: perfData.domContentLoadedEventEnd - perfData.fetchStart,
};
sendToAnalytics(metrics);Lighthouse CI
Catch performance regressions.
{
"ci": {
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }]
}
}
}
}Conclusion
Performance optimization is continuous. Measure first, optimize bottlenecks, verify improvements. Focus on Core Web Vitals, optimize critical rendering path, leverage caching, and monitor real-world performance. Fast sites provide better UX and rank higher in search results.