14 KiB
name, description
| name | description |
|---|---|
| web-performance-optimization | Comprehensive guide to web performance optimization techniques, Core Web Vitals, and best practices across frameworks |
Web Performance Optimization Guide
This skill provides comprehensive knowledge about web performance optimization, Core Web Vitals, and practical techniques for improving website speed and user experience.
Core Web Vitals
1. Largest Contentful Paint (LCP)
What it measures: Loading performance - time until the largest content element is visible.
Good: < 2.5s | Needs Improvement: 2.5s - 4.0s | Poor: > 4.0s
Common causes of poor LCP:
- Slow server response times
- Render-blocking JavaScript and CSS
- Slow resource load times
- Client-side rendering
Optimization strategies:
<!-- Preload critical resources -->
<link rel="preload" as="image" href="hero-image.jpg">
<link rel="preload" as="font" href="font.woff2" type="font/woff2" crossorigin>
<!-- Optimize images -->
<img src="hero.webp" alt="Hero" loading="eager" fetchpriority="high">
<!-- Lazy load below-the-fold images -->
<img src="image.jpg" loading="lazy" alt="Description">
// Implement adaptive loading based on network
if (navigator.connection && navigator.connection.effectiveType === '4g') {
// Load high-res images
} else {
// Load lower-res images
}
2. First Input Delay (FID) / Interaction to Next Paint (INP)
What it measures: Interactivity - time from first user interaction to browser response.
Good (FID): < 100ms | Needs Improvement: 100ms - 300ms | Poor: > 300ms Good (INP): < 200ms | Needs Improvement: 200ms - 500ms | Poor: > 500ms
Common causes:
- Heavy JavaScript execution
- Long tasks blocking main thread
- Large bundle sizes
Optimization strategies:
// Code splitting - load only what's needed
import('./heavy-module.js').then(module => {
module.init();
});
// Use web workers for heavy computation
const worker = new Worker('worker.js');
worker.postMessage({data: largeDataset});
// Debounce expensive operations
const debouncedSearch = debounce((query) => {
performSearch(query);
}, 300);
// Use requestIdleCallback for non-critical work
requestIdleCallback(() => {
analytics.track('page_view');
});
3. Cumulative Layout Shift (CLS)
What it measures: Visual stability - unexpected layout shifts during page load.
Good: < 0.1 | Needs Improvement: 0.1 - 0.25 | Poor: > 0.25
Common causes:
- Images without dimensions
- Dynamically injected content
- Web fonts causing FOIT/FOUT
- Ads, embeds, iframes
Optimization strategies:
<!-- Always specify image dimensions -->
<img src="image.jpg" width="800" height="600" alt="Description">
<!-- Use aspect-ratio CSS for responsive images -->
<style>
.responsive-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
</style>
<!-- Reserve space for dynamic content -->
<div style="min-height: 400px;">
<!-- Content loaded here -->
</div>
<!-- Font loading strategies -->
<link rel="preload" as="font" href="font.woff2" type="font/woff2" crossorigin>
<style>
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* or optional */
}
</style>
Performance Optimization Techniques
1. Resource Optimization
Image Optimization
# Convert to WebP (90% smaller than JPEG)
cwebp input.jpg -q 80 -o output.webp
# Generate responsive images
convert input.jpg -resize 400x output-400w.jpg
convert input.jpg -resize 800x output-800w.jpg
convert input.jpg -resize 1200x output-1200w.jpg
<!-- Responsive images with WebP -->
<picture>
<source type="image/webp" srcset="image-400.webp 400w, image-800.webp 800w, image-1200.webp 1200w">
<source type="image/jpeg" srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w">
<img src="image-800.jpg" alt="Description" loading="lazy">
</picture>
JavaScript Optimization
// Tree shaking - import only what you need
import { debounce } from 'lodash-es'; // Good
// import _ from 'lodash'; // Bad - imports everything
// Dynamic imports for route-based code splitting
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
// Minimize third-party scripts
// Use Partytown for offloading to web worker
<script type="text/partytown">
// Analytics, ads, etc. run in worker
</script>
CSS Optimization
<!-- Inline critical CSS -->
<style>
/* Above-the-fold styles only */
.header { display: flex; }
.hero { min-height: 400px; }
</style>
<!-- Defer non-critical CSS -->
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>
<!-- Remove unused CSS -->
<!-- Use PurgeCSS, UnCSS, or built-in framework tools -->
2. Network Optimization
Compression
# Enable gzip/brotli compression (nginx)
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;
HTTP/2 & HTTP/3
# Enable HTTP/2
listen 443 ssl http2;
# Enable HTTP/3 (QUIC)
listen 443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400';
Caching Strategy
// Service Worker caching
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll([
'/',
'/styles.css',
'/script.js',
'/logo.svg'
]);
})
);
});
// Cache-first strategy for static assets
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
# HTTP caching headers
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
3. Rendering Optimization
Critical Rendering Path
<!-- Optimize the critical rendering path -->
<!DOCTYPE html>
<html>
<head>
<!-- Preconnect to required origins -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://analytics.example.com">
<!-- Inline critical CSS -->
<style>/* Critical styles */</style>
<!-- Async non-critical CSS -->
<link rel="preload" href="non-critical.css" as="style" onload="this.rel='stylesheet'">
</head>
<body>
<!-- Content -->
<!-- Defer JavaScript -->
<script defer src="app.js"></script>
</body>
</html>
Server-Side Rendering (SSR) vs Client-Side Rendering (CSR)
// Next.js - Hybrid approach
export async function getServerSideProps() {
// Fetch data on server
const data = await fetch('https://api.example.com/data');
return { props: { data } };
}
// Static generation for faster TTFB
export async function getStaticProps() {
const data = await fetch('https://api.example.com/data');
return {
props: { data },
revalidate: 60 // ISR - revalidate every 60s
};
}
4. Framework-Specific Optimizations
React
// Lazy load components
const HeavyComponent = lazy(() => import('./HeavyComponent'));
// Memoization
const MemoizedComponent = React.memo(({ data }) => {
return <div>{data}</div>;
});
// useMemo for expensive computations
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// useCallback for function references
const handleClick = useCallback(() => {
doSomething(a);
}, [a]);
// Virtualization for long lists
import { FixedSizeList } from 'react-window';
<FixedSizeList
height={400}
itemCount={1000}
itemSize={50}
>
{Row}
</FixedSizeList>
Vue
// Async components
const AsyncComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
);
// Keep-alive for component caching
<keep-alive>
<component :is="currentView" />
</keep-alive>
// v-once for static content
<div v-once>{{ staticContent }}</div>
// Virtual scrolling
import { RecycleScroller } from 'vue-virtual-scroller';
<RecycleScroller
:items="items"
:item-size="50"
>
</RecycleScroller>
Angular
// Lazy loading routes
const routes: Routes = [
{
path: 'feature',
loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
}
];
// OnPush change detection
@Component({
selector: 'app-component',
changeDetection: ChangeDetectionStrategy.OnPush
})
// TrackBy for *ngFor
<div *ngFor="let item of items; trackBy: trackByFn">
{{ item.name }}
</div>
trackByFn(index, item) {
return item.id;
}
Database & Backend Optimization
N+1 Query Problem
// Bad - N+1 queries
const users = await User.findAll();
for (const user of users) {
const posts = await Post.findAll({ where: { userId: user.id } });
}
// Good - Single query with join
const users = await User.findAll({
include: [{ model: Post }]
});
Caching Strategies
// Redis caching
const cached = await redis.get(`user:${id}`);
if (cached) {
return JSON.parse(cached);
}
const user = await db.users.findById(id);
await redis.set(`user:${id}`, JSON.stringify(user), 'EX', 3600);
return user;
// Memoization
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
};
Database Indexing
-- Create indexes for frequently queried columns
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_user_id ON posts(user_id);
-- Composite indexes for multi-column queries
CREATE INDEX idx_posts_user_date ON posts(user_id, created_at);
-- Analyze query performance
EXPLAIN ANALYZE SELECT * FROM posts WHERE user_id = 123;
Performance Monitoring
Real User Monitoring (RUM)
// Web Vitals API
import {getCLS, getFID, getFCP, getLCP, getTTFB} from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);
// Send to analytics
function sendToAnalytics({name, value, id}) {
analytics.track('web-vital', {
metric: name,
value: Math.round(value),
id
});
}
getCLS(sendToAnalytics);
getLCP(sendToAnalytics);
Performance Observer API
// Observe long tasks
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn('Long task detected:', entry);
}
}
});
observer.observe({entryTypes: ['longtask']});
// Monitor resource timing
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Resource:', entry.name, 'Duration:', entry.duration);
}
}).observe({entryTypes: ['resource']});
Common Performance Anti-Patterns
1. Blocking the Main Thread
// Bad - blocks UI
for (let i = 0; i < 1000000; i++) {
// Heavy computation
}
// Good - chunk work
function processInChunks(data, chunkSize = 100) {
let index = 0;
function processChunk() {
const chunk = data.slice(index, index + chunkSize);
// Process chunk
index += chunkSize;
if (index < data.length) {
requestIdleCallback(processChunk);
}
}
processChunk();
}
2. Memory Leaks
// Bad - creates memory leak
class Component {
constructor() {
setInterval(() => {
this.updateState();
}, 1000);
}
}
// Good - cleanup
class Component {
constructor() {
this.interval = setInterval(() => {
this.updateState();
}, 1000);
}
destroy() {
clearInterval(this.interval);
}
}
3. Excessive Re-renders
// Bad - re-renders on every parent update
function Child({ items }) {
return items.map(item => <div key={item.id}>{item.name}</div>);
}
// Good - memoized
const Child = React.memo(function Child({ items }) {
return items.map(item => <div key={item.id}>{item.name}</div>);
}, (prevProps, nextProps) => {
return prevProps.items === nextProps.items;
});
Performance Budget
Set and enforce performance budgets:
{
"budgets": [
{
"resourceSizes": [
{"resourceType": "script", "budget": 300},
{"resourceType": "image", "budget": 500},
{"resourceType": "stylesheet", "budget": 50}
],
"resourceCounts": [
{"resourceType": "third-party", "budget": 10}
],
"timings": [
{"metric": "interactive", "budget": 3000},
{"metric": "first-contentful-paint", "budget": 1500}
]
}
]
}
Testing Tools
- Lighthouse: Automated audits
- WebPageTest: Real device testing
- Chrome DevTools: Performance profiling
- bundlephobia.com: Check package sizes
- web.dev: Best practices and guides
Key Takeaways
- Measure first: Use Lighthouse, Web Vitals, RUM
- Optimize critical path: Inline critical CSS, defer JS
- Reduce bundle size: Code splitting, tree shaking
- Optimize images: WebP, lazy loading, responsive images
- Cache effectively: Service workers, HTTP caching
- Minimize main thread work: Web workers, chunking
- Monitor continuously: Real user monitoring
- Set budgets: Enforce performance standards