Web Performance Optimization — Complete Guide to Fast Websites
Web performance optimization (WPO) is the practice of making websites load faster and run smoother — directly impacting user experience, conversion rates, and search engine rankings.
What You’ll Learn
- Core Web Vitals and how to measure them
- Lazy loading images, scripts, and components
- Code splitting and bundle optimization
- Caching strategies and CDN fundamentals
- Image optimization techniques
Why It Matters
A 1-second delay in page load time reduces conversions by 7%. Google uses Core Web Vitals as ranking signals — slow sites rank lower. For every dollar invested in performance, businesses see $100 in return. Users expect pages to load in under 2 seconds; after 3 seconds, 53% of mobile users abandon the page.
Real-world use: Doda Browser includes a built-in performance tracker showing how fast each site loads. Durga Antivirus Pro’s web scanner must analyze pages quickly without slowing down the browsing experience — performance optimization is baked into the scanning engine itself.
Core Web Vitals
Google’s Core Web Vitals measure real-world user experience with three key metrics:
flowchart LR
A[LCP<br/>Largest Contentful Paint<br/>< 2.5s] --> B[FID<br/>First Input Delay<br/>< 100ms]
B --> C[CLS<br/>Cumulative Layout Shift<br/>< 0.1]
style A fill:#4af,color:#fff
style B fill:#f90,color:#fff
style C fill:#4a4,color:#fff
LCP (Largest Contentful Paint)
Measures loading performance — how long the largest content element takes to appear. Elements include images, video posters, and text blocks.
Target: < 2.5 seconds
<!-- Optimize LCP by preloading the hero image -->
<link rel="preload" href="hero.webp" as="image" fetchpriority="high">
<!-- Reduce render-blocking resources -->
<link rel="preload" href="critical.css" as="style">
<link rel="preconnect" href="https://fonts.googleapis.com">FID (First Input Delay)
Measures interactivity — the time between a user’s first interaction (click, tap, key press) and the browser’s response. Will be replaced by INP (Interaction to Next Paint) in 2024+.
Target: < 100ms
// Break up long tasks with setTimeout or scheduler.yield
function processData(largeArray) {
let index = 0;
function processChunk() {
const chunk = largeArray.slice(index, index + 50);
chunk.forEach(item => renderItem(item));
index += 50;
if (index < largeArray.length) {
setTimeout(processChunk, 0); // Yield to main thread
}
}
processChunk();
}CLS (Cumulative Layout Shift)
Measures visual stability — how much the page layout shifts as content loads. Layout shifts happen when images, ads, or embeds load without reserved space.
Target: < 0.1
<!-- Prevent layout shifts by setting dimensions -->
<img src="photo.webp" width="800" height="600" loading="lazy"
style="aspect-ratio: 800/600">
<!-- Reserve space for dynamic content -->
<div style="min-height: 300px" id="ad-container"></div>Measuring Performance
// Use Performance API for custom measurements
performance.mark('start-scan');
// ... scanning logic runs ...
performance.mark('end-scan');
performance.measure('scan-duration', 'start-scan', 'end-scan');
const measurements = performance.getEntriesByType('measure');
console.log('Scan took:', measurements[0].duration.toFixed(2), 'ms');
// Web Vitals via web-vitals library
import { onLCP, onFID, onCLS } from 'web-vitals';
onLCP(console.log);
onFID(console.log);
onCLS(console.log);Lazy Loading
Lazy loading defers non-critical resources until they’re needed — saving bandwidth and speeding initial page load.
Image Lazy Loading
<!-- Native lazy loading (browser-supported) -->
<img src="photo.webp" loading="lazy" width="800" height="600"
alt="Description">
<!-- Intersection Observer for custom lazy loading -->
<img data-src="photo.webp" class="lazy" width="800" height="600"
alt="Description">// Custom lazy loading with Intersection Observer
document.addEventListener('DOMContentLoaded', () => {
const lazyImages = document.querySelectorAll('.lazy');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
}, {
rootMargin: '200px' // Start loading before visible
});
lazyImages.forEach(img => observer.observe(img));
});Component Lazy Loading
// Dynamic imports for code splitting (webpack/vite)
const HeavyComponent = () => {
const [Component, setComponent] = useState(null);
useEffect(() => {
import('./HeavyChart.js').then(mod => {
setComponent(() => mod.default);
});
}, []);
return Component ? <Component /> : <Skeleton />;
};Code Splitting
Code splitting breaks your JavaScript bundle into smaller chunks loaded on demand.
Route-Based Splitting
// React Router with lazy loading
const Home = lazy(() => import('./routes/Home'));
const Dashboard = lazy(() => import('./routes/Dashboard'));
const Settings = lazy(() => import('./routes/Settings'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}Vendor Bundle Splitting
// vite.config.js — separate vendor and app code
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
charts: ['chart.js', 'd3.js'],
utils: ['lodash', 'date-fns']
}
}
}
}
};Caching Strategies
Caching stores copies of resources so repeat visits load instantly.
Browser Caching
<!-- Service worker caching (PWA) -->
<!-- or use HTTP cache headers --># Nginx cache headers for static assets
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location /images/ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
# HTML should not be cached aggressively
location / {
expires -1;
add_header Cache-Control "no-cache";
}Service Worker Caching
// Cache-first strategy for static assets
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/static/')) {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).then((response) => {
return caches.open('static-v1').then((cache) => {
cache.put(event.request, response.clone());
return response;
});
});
})
);
}
});CDN (Content Delivery Network)
A CDN serves your static files from servers located near your users, reducing latency dramatically.
Without CDN: user in India → server in USA → 300ms latency
With CDN: user in India → Mumbai edge server → 10ms latencyPopular CDNs: Cloudflare, AWS CloudFront, Netlify, Vercel.
Image Optimization
Images are the largest resources on most pages. Optimizing them gives the biggest performance wins.
Optimization Techniques
<!-- WebP format with AVIF fallback -->
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Photo" loading="lazy"
width="800" height="600">
</picture>
<!-- Responsive images (already covered in Responsive Design) -->
<img src="photo-800.webp"
srcset="photo-400.webp 400w, photo-800.webp 800w, photo-1200.webp 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="Photo" loading="lazy" width="800" height="600">// Client-side image compression before upload
async function compressImage(file, maxWidth = 1920, quality = 0.8) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const scale = Math.min(maxWidth / img.width, 1);
canvas.width = img.width * scale;
canvas.height = img.height * scale;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
canvas.toBlob(resolve, 'image/webp', quality);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
}Performance Budget
Set a performance budget to prevent regressions:
| Resource | Budget |
|---|---|
| Total page weight | < 500 KB |
| JavaScript | < 150 KB (gzipped) |
| CSS | < 50 KB (gzipped) |
| Images | < 200 KB |
| Time to Interactive | < 3s on 3G |
| LCP | < 2.5s |
| CLS | < 0.1 |
Build Tool Optimization
Webpack and Vite offer built-in optimization:
// vite.config.js
export default {
build: {
minify: 'terser', // Minify JavaScript
cssMinify: 'lightningcss', // Minify CSS
target: 'es2020', // Modern browsers
rollupOptions: {
output: {
manualChunks: undefined, // Auto code splitting
}
}
}
};Common Errors
- Not compressing images — Uncompressed images are typically 3-5x larger than optimized WebP versions. Always compress before deploying.
- Loading everything on initial page load — Defer non-critical CSS, lazy-load below-fold images, and code-split JavaScript.
- No cache headers — Without caching, returning visitors download everything again. Set appropriate
Cache-Controlheaders. - Render-blocking JavaScript — Scripts without
deferorasyncblock HTML parsing. Use<script defer>for non-critical scripts. - Not measuring before optimizing — Don’t guess what’s slow. Measure with Lighthouse, WebPageTest, or Chrome DevTools first.
Practice Questions
What are Core Web Vitals and why do they matter? Core Web Vitals (LCP, FID, CLS) are Google’s metrics for real-world user experience. They directly impact search rankings and user satisfaction.
How does lazy loading improve performance? Lazy loading defers off-screen resources (images, scripts, components) until the user needs them, reducing initial page weight and load time.
What’s the difference between cache-first and network-first strategies? Cache-first serves from cache and updates in the background (fast but potentially stale). Network-first tries the network first, falling back to cache (fresh but slower on repeat visits).
Why are CDNs important for global performance? CDNs reduce latency by serving content from edge servers close to users. A user in Tokyo gets served from Tokyo, not from your origin server in Virginia.
What is a performance budget? A performance budget sets maximum limits for page weight, load time, and other metrics. It prevents performance regressions and keeps teams accountable.
Challenge
Audit a web page using Lighthouse in Chrome DevTools, identify the top three performance issues, and implement fixes. Measure the before and after scores. Focus on image optimization, render-blocking resources, and cache policy.
Real-World Task
Optimize the Durga Antivirus Pro web scanner UI to load under 2 seconds on a 3G connection. Implement image lazy loading for threat thumbnails, code-split the charting library into a separate chunk, add cache headers for static assets, and serve everything through a CDN. Document the before/after performance metrics.
Related: CDN Guide | Related: Lazy Loading | Related: Progressive Web Apps
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro