History
How :
Web performance optimization evolved with the growth of the internet and user expectations.
Early 2000s: Focus on image optimization and minification.
2010s: Introduction of CDNs, HTTP/2, and modern bundlers.
2020s: Core Web Vitals, edge computing, and modern frameworks with built-in optimization.
Who :
Google — Introduced Core Web Vitals and PageSpeed Insights.
Cloudflare, Akamai — Pioneered CDN technology.
Web Performance Working Group — W3C standards for performance APIs.
Why :
Faster websites improve user experience and engagement.
Performance directly impacts SEO rankings and conversion rates.
Mobile users demand fast loading on slower networks.
Every 100ms delay can reduce conversions by 7%.
Introduction
Advantages
Better User Experience — Fast loading keeps users engaged.
Higher Conversion Rates — Speed directly correlates with sales and signups.
Improved SEO — Google ranks faster sites higher.
Reduced Bounce Rate — Users stay longer on fast sites.
Lower Server Costs — Efficient data transfer reduces bandwidth usage.
Mobile Performance — Critical for users on slower networks.
Competitive Advantage — Faster than competitors = more users.
Key Metrics
FCP (First Contentful Paint) — When first content appears (< 1.8s good).
LCP (Largest Contentful Paint) — When main content loads (< 2.5s good).
FID (First Input Delay) — Time until page becomes interactive (< 100ms good).
CLS (Cumulative Layout Shift) — Visual stability (< 0.1 good).
TTFB (Time to First Byte) — Server response time (< 600ms good).
TTI (Time to Interactive) — When page is fully interactive (< 3.8s good).
Fast Backend to Frontend Data Loading
API Optimization Techniques
// 1. Use GraphQL for precise data fetching (avoid over-fetching)
const query = `
query GetUser {
user(id: "123") {
name
email
// Only fetch needed fields
}
}
` ;
// 2. REST API with field selection
fetch ( '/api/users/123?fields=name,email' )
. then ( res => res. json ())
. then ( data => console. log (data));
// 3. Pagination for large datasets
fetch ( '/api/posts?page=1&limit=20' )
. then ( res => res. json ());
// 4. Compression (gzip/brotli) - Server-side
// Express.js example
const compression = require ( 'compression' );
app. use ( compression ());
// 5. Response caching headers
res. setHeader ( 'Cache-Control' , 'public, max-age=3600' );
res. setHeader ( 'ETag' , 'unique-version-id' );
Data Fetching Strategies
// 1. Parallel requests (fetch multiple at once)
const [ users , posts , comments ] = await Promise . all ([
fetch ( '/api/users' ). then ( r => r. json ()),
fetch ( '/api/posts' ). then ( r => r. json ()),
fetch ( '/api/comments' ). then ( r => r. json ())
]);
// 2. Sequential requests (when data depends on previous)
const user = await fetch ( '/api/user/123' ). then ( r => r. json ());
const posts = await fetch ( `/api/users/${ user . id }/posts` ). then ( r => r. json ());
// 3. Streaming data (Server-Sent Events)
const eventSource = new EventSource ( '/api/stream' );
eventSource. onmessage = ( event ) => {
const data = JSON . parse (event.data);
updateUI (data);
};
// 4. WebSocket for real-time data
const socket = new WebSocket ( 'wss://api.example.com' );
socket. onmessage = ( event ) => {
const data = JSON . parse (event.data);
updateUI (data);
};
// 5. Incremental loading (load critical data first)
async function loadData () {
// Load critical data immediately
const critical = await fetch ( '/api/critical' ). then ( r => r. json ());
renderCritical (critical);
// Load non-critical data after
const additional = await fetch ( '/api/additional' ). then ( r => r. json ());
renderAdditional (additional);
}
Request Optimization
// 1. Debouncing API calls (search input)
function debounce ( func , delay ) {
let timeoutId;
return function ( ... args ) {
clearTimeout (timeoutId);
timeoutId = setTimeout (() => func. apply ( this , args), delay);
};
}
const searchAPI = debounce (( query ) => {
fetch ( `/api/search?q=${ query }` )
. then ( res => res. json ())
. then ( data => displayResults (data));
}, 300 );
// 2. Request cancellation (AbortController)
let controller = new AbortController ();
function searchWithCancel ( query ) {
// Cancel previous request
controller. abort ();
controller = new AbortController ();
fetch ( `/api/search?q=${ query }` , { signal: controller.signal })
. then ( res => res. json ())
. then ( data => displayResults (data))
. catch ( err => {
if (err.name === 'AbortError' ) {
console. log ( 'Request cancelled' );
}
});
}
// 3. Request batching (combine multiple requests)
class RequestBatcher {
constructor ( batchFn , delay = 50 ) {
this .batchFn = batchFn;
this .delay = delay;
this .queue = [];
this .timeoutId = null ;
}
add ( request ) {
return new Promise (( resolve , reject ) => {
this .queue. push ({ request, resolve, reject });
if ( ! this .timeoutId) {
this .timeoutId = setTimeout (() => this . flush (), this .delay);
}
});
}
flush () {
const batch = this .queue. splice ( 0 );
this .timeoutId = null ;
this . batchFn (batch. map ( item => item.request))
. then ( results => {
batch. forEach (( item , index ) => {
item. resolve (results[index]);
});
})
. catch ( error => {
batch. forEach ( item => item. reject (error));
});
}
}
// Usage
const batcher = new RequestBatcher ( async ( ids ) => {
const response = await fetch ( '/api/users/batch' , {
method: 'POST' ,
body: JSON . stringify ({ ids })
});
return response. json ();
});
// Multiple calls get batched automatically
batcher. add ( 1 ). then ( user => console. log (user));
batcher. add ( 2 ). then ( user => console. log (user));
batcher. add ( 3 ). then ( user => console. log (user));
Backend Optimization
// Node.js/Express Backend Optimization
// 1. Database query optimization
// Bad: N+1 query problem
const users = await User. findAll ();
for ( const user of users) {
user.posts = await Post. findAll ({ where: { userId: user.id } });
}
// Good: Use joins/includes
const users = await User. findAll ({
include: [{ model: Post }]
});
// 2. Database indexing
// Create index on frequently queried fields
// SQL: CREATE INDEX idx_user_email ON users(email);
// 3. Response compression
const compression = require ( 'compression' );
app. use ( compression ({
level: 6 , // Compression level (0-9)
threshold: 1024 // Only compress responses > 1KB
}));
// 4. Caching with Redis
const redis = require ( 'redis' );
const client = redis. createClient ();
app. get ( '/api/users/:id' , async ( req , res ) => {
const cacheKey = `user:${ req . params . id }` ;
// Check cache first
const cached = await client. get (cacheKey);
if (cached) {
return res. json ( JSON . parse (cached));
}
// Fetch from database
const user = await User. findByPk (req.params.id);
// Store in cache (expire after 1 hour)
await client. setEx (cacheKey, 3600 , JSON . stringify (user));
res. json (user);
});
// 5. Connection pooling
const { Pool } = require ( 'pg' );
const pool = new Pool ({
max: 20 , // Maximum connections
idleTimeoutMillis: 30000 ,
connectionTimeoutMillis: 2000
});
// 6. Rate limiting
const rateLimit = require ( 'express-rate-limit' );
const limiter = rateLimit ({
windowMs: 15 * 60 * 1000 , // 15 minutes
max: 100 // Limit each IP to 100 requests per window
});
app. use ( '/api/' , limiter);
// 7. Async processing for heavy tasks
const Queue = require ( 'bull' );
const emailQueue = new Queue ( 'email' );
app. post ( '/api/send-email' , async ( req , res ) => {
// Add to queue instead of processing immediately
await emailQueue. add ({ email: req.body.email });
res. json ({ message: 'Email queued' });
});
// Process queue in background
emailQueue. process ( async ( job ) => {
await sendEmail (job.data.email);
});
// 8. HTTP/2 Server Push
const http2 = require ( 'http2' );
const server = http2. createSecureServer (options);
server. on ( 'stream' , ( stream , headers ) => {
// Push critical resources
stream. pushStream ({ ':path' : '/styles.css' }, ( err , pushStream ) => {
pushStream. respondWithFile ( 'styles.css' );
});
});
Data Serialization & Transfer
// 1. Use efficient data formats
// JSON (standard, human-readable)
const jsonData = JSON . stringify ({ name: 'John' , age: 30 });
// Size: ~25 bytes
// MessagePack (binary, smaller)
const msgpack = require ( 'msgpack5' )();
const packed = msgpack. encode ({ name: 'John' , age: 30 });
// Size: ~15 bytes (40% smaller)
// Protocol Buffers (fastest, smallest)
// Define schema in .proto file, compile, use
// 2. Remove unnecessary data
// Bad: Send entire object
res. json (user); // Includes password hash, internal IDs, etc.
// Good: Send only needed fields
res. json ({
id: user.id,
name: user.name,
email: user.email
});
// 3. Use ETags for conditional requests
const etag = require ( 'etag' );
app. get ( '/api/data' , ( req , res ) => {
const data = getData ();
const tag = etag ( JSON . stringify (data));
if (req.headers[ 'if-none-match' ] === tag) {
return res. status ( 304 ). end (); // Not Modified
}
res. setHeader ( 'ETag' , tag);
res. json (data);
});
Fast Page Navigation & Routing
Client-Side Routing (SPA)
// React Router - Fast client-side navigation
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom' ;
function App () {
return (
< BrowserRouter >
< nav >
< Link to = "/" >Home</ Link >
< Link to = "/about" >About</ Link >
</ nav >
< Routes >
< Route path = "/" element = {< Home />} />
< Route path = "/about" element = {< About />} />
</ Routes >
</ BrowserRouter >
);
}
// Vue Router
import { createRouter, createWebHistory } from 'vue-router' ;
const router = createRouter ({
history: createWebHistory (),
routes: [
{ path: '/' , component: Home },
{ path: '/about' , component: About }
]
});
// Vanilla JavaScript SPA Router
class Router {
constructor ( routes ) {
this .routes = routes;
this . init ();
}
init () {
// Handle link clicks
document. addEventListener ( 'click' , ( e ) => {
if (e.target. matches ( '[data-link]' )) {
e. preventDefault ();
this . navigate (e.target.href);
}
});
// Handle back/forward buttons
window. addEventListener ( 'popstate' , () => {
this . loadRoute (window.location.pathname);
});
// Load initial route
this . loadRoute (window.location.pathname);
}
navigate ( url ) {
history. pushState ( null , null , url);
this . loadRoute (url);
}
loadRoute ( path ) {
const route = this .routes[path] || this .routes[ '/404' ];
document. getElementById ( 'app' ).innerHTML = route ();
}
}
// Usage
const router = new Router ({
'/' : () => '<h1>Home</h1>' ,
'/about' : () => '<h1>About</h1>' ,
'/404' : () => '<h1>Not Found</h1>'
});
Prefetching & Preloading
<!-- 1. DNS Prefetch (resolve domain early) -->
< link rel = "dns-prefetch" href = "https://api.example.com" >
<!-- 2. Preconnect (establish connection early) -->
< link rel = "preconnect" href = "https://cdn.example.com" >
<!-- 3. Prefetch (load resource for next navigation) -->
< link rel = "prefetch" href = "/next-page.html" >
< link rel = "prefetch" href = "/api/data.json" >
<!-- 4. Preload (load critical resource immediately) -->
< link rel = "preload" href = "/critical.css" as = "style" >
< link rel = "preload" href = "/hero.jpg" as = "image" >
< link rel = "preload" href = "/font.woff2" as = "font" crossorigin >
<!-- 5. Prerender (render entire page in background) -->
< link rel = "prerender" href = "/next-page.html" >
// JavaScript prefetching
// 1. Prefetch on hover (anticipate navigation)
document. querySelectorAll ( 'a' ). forEach ( link => {
link. addEventListener ( 'mouseenter' , () => {
const url = link.href;
const prefetchLink = document. createElement ( 'link' );
prefetchLink.rel = 'prefetch' ;
prefetchLink.href = url;
document.head. appendChild (prefetchLink);
});
});
// 2. Intersection Observer prefetching
const observer = new IntersectionObserver (( entries ) => {
entries. forEach ( entry => {
if (entry.isIntersecting) {
const link = entry.target;
prefetchPage (link.href);
observer. unobserve (link);
}
});
});
document. querySelectorAll ( 'a[data-prefetch]' ). forEach ( link => {
observer. observe (link);
});
function prefetchPage ( url ) {
fetch (url, { priority: 'low' })
. then ( res => res. text ())
. then ( html => {
// Cache in memory or service worker
console. log ( 'Prefetched:' , url);
});
}
// 3. Quicklink library (automatic prefetching)
import quicklink from 'quicklink' ;
quicklink. listen ({
timeout: 2000 , // Wait 2s after page load
priority: true , // Use high priority fetch
origins: [location.hostname] // Only prefetch same origin
});
Code Splitting & Lazy Loading
// React lazy loading
import React, { lazy, Suspense } from 'react' ;
// Lazy load component
const About = lazy (() => import ( './pages/About' ));
const Dashboard = lazy (() => import ( './pages/Dashboard' ));
function App () {
return (
< Suspense fallback = {< div >Loading...</ div >}>
< Routes >
< Route path = "/" element = {< Home />} />
< Route path = "/about" element = {< About />} />
< Route path = "/dashboard" element = {< Dashboard />} />
</ Routes >
</ Suspense >
);
}
// Vue lazy loading
const routes = [
{
path: '/' ,
component : () => import ( './views/Home.vue' )
},
{
path: '/about' ,
component : () => import ( './views/About.vue' )
}
];
// Webpack dynamic imports
button. addEventListener ( 'click' , async () => {
const module = await import ( './heavy-module.js' );
module . doSomething ();
});
// Route-based code splitting (Webpack)
const routes = {
'/' : () => import ( './pages/home' ),
'/about' : () => import ( './pages/about' ),
'/contact' : () => import ( './pages/contact' )
};
async function loadRoute ( path ) {
const module = await routes[path]();
renderPage ( module .default);
}
Page Transition Optimization
// 1. View Transitions API (Modern browsers)
async function navigateWithTransition ( url ) {
if ( ! document.startViewTransition) {
// Fallback for unsupported browsers
return navigate (url);
}
const transition = document. startViewTransition ( async () => {
await navigate (url);
});
await transition.finished;
}
// CSS for view transitions
// ::view-transition-old(root) {
// animation: fade-out 0.3s ease-out;
// }
// ::view-transition-new(root) {
// animation: fade-in 0.3s ease-in;
// }
// 2. Optimistic UI updates
async function updateData ( newData ) {
// Update UI immediately (optimistic)
updateUI (newData);
try {
// Send to server
await fetch ( '/api/update' , {
method: 'POST' ,
body: JSON . stringify (newData)
});
} catch (error) {
// Revert on error
revertUI ();
showError ( 'Update failed' );
}
}
// 3. Skeleton screens (perceived performance)
function showSkeleton () {
return `
<div class="skeleton">
<div class="skeleton-line"></div>
<div class="skeleton-line"></div>
<div class="skeleton-line short"></div>
</div>
` ;
}
async function loadPage () {
// Show skeleton immediately
document. getElementById ( 'content' ).innerHTML = showSkeleton ();
// Load actual content
const data = await fetch ( '/api/data' ). then ( r => r. json ());
document. getElementById ( 'content' ).innerHTML = renderContent (data);
}
// 4. Progressive enhancement
// Start with basic HTML, enhance with JavaScript
< a href = "/page" class = "enhanced-link" >
Go to page
</ a >
// Enhance with JavaScript
document. querySelectorAll ( '.enhanced-link' ). forEach ( link => {
link. addEventListener ( 'click' , ( e ) => {
e. preventDefault ();
navigateWithTransition (link.href);
});
});
Service Worker Caching
// service-worker.js
const CACHE_NAME = 'v1' ;
const urlsToCache = [
'/' ,
'/styles.css' ,
'/script.js' ,
'/logo.png'
];
// Install service worker and cache assets
self. addEventListener ( 'install' , ( event ) => {
event. waitUntil (
caches. open ( CACHE_NAME )
. then ( cache => cache. addAll (urlsToCache))
);
});
// Intercept fetch requests
self. addEventListener ( 'fetch' , ( event ) => {
event. respondWith (
caches. match (event.request)
. then ( response => {
// Return cached version or fetch from network
return response || fetch (event.request);
})
);
});
// Cache-first strategy (for static assets)
self. addEventListener ( 'fetch' , ( event ) => {
event. respondWith (
caches. match (event.request)
. then ( cached => cached || fetch (event.request))
);
});
// Network-first strategy (for dynamic data)
self. addEventListener ( 'fetch' , ( event ) => {
event. respondWith (
fetch (event.request)
. catch (() => caches. match (event.request))
);
});
// Stale-while-revalidate (best of both)
self. addEventListener ( 'fetch' , ( event ) => {
event. respondWith (
caches. open ( CACHE_NAME ). then ( cache => {
return cache. match (event.request). then ( cached => {
const fetchPromise = fetch (event.request). then ( response => {
cache. put (event.request, response. clone ());
return response;
});
return cached || fetchPromise;
});
})
);
});
// Register service worker
if ( 'serviceWorker' in navigator) {
navigator.serviceWorker. register ( '/service-worker.js' )
. then ( reg => console. log ( 'Service Worker registered' ))
. catch ( err => console. error ( 'Service Worker failed' , err));
}
Instant Navigation Techniques
// 1. Turbo/Turbolinks (Hotwire)
// Automatically intercepts links and replaces content
import Turbo from '@hotwired/turbo' ;
// Links navigate instantly without full page reload
< a href = "/page" >Fast navigation</ a >
// 2. PJAX (PushState + AJAX)
function pjax ( url ) {
fetch (url)
. then ( res => res. text ())
. then ( html => {
const parser = new DOMParser ();
const doc = parser. parseFromString (html, 'text/html' );
// Replace content
document. getElementById ( 'content' ).innerHTML =
doc. getElementById ( 'content' ).innerHTML;
// Update URL
history. pushState ( null , '' , url);
// Update title
document.title = doc.title;
});
}
// 3. Instant.page (automatic prefetching)
// Just include the script - it handles everything
< script src = "//instant.page/5.1.1" type = "module" ></ script >
// 4. Custom instant navigation
class InstantNav {
constructor () {
this .cache = new Map ();
this . init ();
}
init () {
// Prefetch on hover
document. addEventListener ( 'mouseover' , ( e ) => {
if (e.target.tagName === 'A' ) {
this . prefetch (e.target.href);
}
});
// Navigate on click
document. addEventListener ( 'click' , ( e ) => {
if (e.target.tagName === 'A' ) {
e. preventDefault ();
this . navigate (e.target.href);
}
});
}
async prefetch ( url ) {
if ( this .cache. has (url)) return ;
const response = await fetch (url);
const html = await response. text ();
this .cache. set (url, html);
}
async navigate ( url ) {
let html = this .cache. get (url);
if ( ! html) {
const response = await fetch (url);
html = await response. text ();
}
const parser = new DOMParser ();
const doc = parser. parseFromString (html, 'text/html' );
// Replace content with fade effect
const content = document. getElementById ( 'content' );
content.style.opacity = '0' ;
setTimeout (() => {
content.innerHTML = doc. getElementById ( 'content' ).innerHTML;
content.style.opacity = '1' ;
history. pushState ( null , '' , url);
document.title = doc.title;
}, 150 );
}
}
new InstantNav ();
Caching Strategies
Browser Caching
// Server-side cache headers (Node.js/Express)
// 1. Cache static assets for 1 year
app. use ( '/static' , express. static ( 'public' , {
maxAge: '1y' ,
immutable: true
}));
// 2. Cache-Control headers
app. get ( '/api/data' , ( req , res ) => {
// No caching (always fetch fresh)
res. setHeader ( 'Cache-Control' , 'no-store' );
// Cache for 5 minutes, revalidate
res. setHeader ( 'Cache-Control' , 'max-age=300, must-revalidate' );
// Cache for 1 hour, can serve stale while revalidating
res. setHeader ( 'Cache-Control' , 'max-age=3600, stale-while-revalidate=86400' );
// Public cache (CDN can cache)
res. setHeader ( 'Cache-Control' , 'public, max-age=3600' );
// Private cache (only browser, not CDN)
res. setHeader ( 'Cache-Control' , 'private, max-age=3600' );
res. json (data);
});
// 3. ETag for conditional requests
const etag = require ( 'etag' );
app. get ( '/api/data' , ( req , res ) => {
const data = getData ();
const tag = etag ( JSON . stringify (data));
// Client sends If-None-Match header
if (req.headers[ 'if-none-match' ] === tag) {
return res. status ( 304 ). end (); // Not Modified - no data sent
}
res. setHeader ( 'ETag' , tag);
res. setHeader ( 'Cache-Control' , 'max-age=0, must-revalidate' );
res. json (data);
});
// 4. Last-Modified header
app. get ( '/api/data' , ( req , res ) => {
const lastModified = new Date ( '2024-01-01' );
if (req.headers[ 'if-modified-since' ]) {
const clientDate = new Date (req.headers[ 'if-modified-since' ]);
if (clientDate >= lastModified) {
return res. status ( 304 ). end ();
}
}
res. setHeader ( 'Last-Modified' , lastModified. toUTCString ());
res. json (data);
});
Client-Side Caching
// 1. Memory cache (simple object)
const cache = {};
async function fetchWithCache ( url ) {
if (cache[url]) {
return cache[url];
}
const response = await fetch (url);
const data = await response. json ();
cache[url] = data;
return data;
}
// 2. LocalStorage cache with expiration
class LocalStorageCache {
set ( key , value , ttl = 3600000 ) { // Default 1 hour
const item = {
value: value,
expiry: Date. now () + ttl
};
localStorage. setItem (key, JSON . stringify (item));
}
get ( key ) {
const itemStr = localStorage. getItem (key);
if ( ! itemStr) return null ;
const item = JSON . parse (itemStr);
if (Date. now () > item.expiry) {
localStorage. removeItem (key);
return null ;
}
return item.value;
}
remove ( key ) {
localStorage. removeItem (key);
}
clear () {
localStorage. clear ();
}
}
// Usage
const cache = new LocalStorageCache ();
async function fetchData ( url ) {
const cached = cache. get (url);
if (cached) return cached;
const response = await fetch (url);
const data = await response. json ();
cache. set (url, data, 300000 ); // Cache for 5 minutes
return data;
}
// 3. IndexedDB cache (for large data)
class IndexedDBCache {
constructor ( dbName = 'cache' , storeName = 'data' ) {
this .dbName = dbName;
this .storeName = storeName;
this .db = null ;
}
async init () {
return new Promise (( resolve , reject ) => {
const request = indexedDB. open ( this .dbName, 1 );
request. onerror = () => reject (request.error);
request. onsuccess = () => {
this .db = request.result;
resolve ();
};
request. onupgradeneeded = ( event ) => {
const db = event.target.result;
if ( ! db.objectStoreNames. contains ( this .storeName)) {
db. createObjectStore ( this .storeName);
}
};
});
}
async set ( key , value ) {
const tx = this .db. transaction ( this .storeName, 'readwrite' );
const store = tx. objectStore ( this .storeName);
store. put (value, key);
return tx.complete;
}
async get ( key ) {
const tx = this .db. transaction ( this .storeName, 'readonly' );
const store = tx. objectStore ( this .storeName);
return store. get (key);
}
}
// 4. Cache API (Service Worker)
async function cacheFirst ( request ) {
const cache = await caches. open ( 'v1' );
const cached = await cache. match (request);
return cached || fetch (request);
}
async function networkFirst ( request ) {
try {
const response = await fetch (request);
const cache = await caches. open ( 'v1' );
cache. put (request, response. clone ());
return response;
} catch (error) {
return caches. match (request);
}
}
CDN & Edge Caching
// Cloudflare Workers example (edge caching)
addEventListener ( 'fetch' , event => {
event. respondWith ( handleRequest (event.request));
});
async function handleRequest ( request ) {
const cache = caches.default;
// Check cache first
let response = await cache. match (request);
if ( ! response) {
// Fetch from origin
response = await fetch (request);
// Cache for 1 hour
const headers = new Headers (response.headers);
headers. set ( 'Cache-Control' , 'public, max-age=3600' );
response = new Response (response.body, {
status: response.status,
statusText: response.statusText,
headers: headers
});
// Store in edge cache
event. waitUntil (cache. put (request, response. clone ()));
}
return response;
}
// CDN cache purging
// Cloudflare API example
async function purgeCache ( urls ) {
const response = await fetch (
`https://api.cloudflare.com/client/v4/zones/${ ZONE_ID }/purge_cache` ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ API_TOKEN }` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ files: urls })
}
);
return response. json ();
}
Resource Optimization
Image Optimization
<!-- 1. Modern image formats -->
< picture >
< source srcset = "image.avif" type = "image/avif" >
< source srcset = "image.webp" type = "image/webp" >
< img src = "image.jpg" alt = "Fallback" >
</ picture >
<!-- 2. Responsive images -->
< img
srcset = "small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
sizes = "(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px"
src = "medium.jpg"
alt = "Responsive image" >
<!-- 3. Lazy loading -->
< img src = "image.jpg" loading = "lazy" alt = "Lazy loaded" >
<!-- 4. Blur placeholder (LQIP - Low Quality Image Placeholder) -->
< img
src = "tiny-blur.jpg"
data-src = "full-image.jpg"
class = "lazy-blur"
alt = "Image" >
// Lazy loading with Intersection Observer
const imageObserver = new IntersectionObserver (( entries ) => {
entries. forEach ( entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList. remove ( 'lazy' );
imageObserver. unobserve (img);
}
});
});
document. querySelectorAll ( 'img.lazy' ). forEach ( img => {
imageObserver. observe (img);
});
// Progressive image loading
function loadProgressiveImage ( img ) {
const lowSrc = img.dataset.lowsrc;
const highSrc = img.dataset.highsrc;
// Load low quality first
img.src = lowSrc;
// Load high quality in background
const highImg = new Image ();
highImg. onload = () => {
img.src = highSrc;
img.classList. add ( 'loaded' );
};
highImg.src = highSrc;
}
JavaScript Optimization
// 1. Code splitting
// Webpack automatically splits at dynamic imports
button. addEventListener ( 'click' , async () => {
const module = await import ( './heavy-feature.js' );
module . init ();
});
// 2. Tree shaking (remove unused code)
// Use ES6 imports (not CommonJS require)
import { usedFunction } from './utils' ; // Only usedFunction is bundled
// 3. Minification (production build)
// Webpack/Vite automatically minifies in production
// 4. Defer/Async script loading
// <script src="script.js" defer></script> // Execute after HTML parsing
// <script src="script.js" async></script> // Execute as soon as loaded
// 5. Remove console.logs in production
// Webpack config
// optimization: {
// minimize: true,
// minimizer: [
// new TerserPlugin({
// terserOptions: {
// compress: {
// drop_console: true
// }
// }
// })
// ]
// }
// 6. Debounce expensive operations
function debounce ( func , delay ) {
let timeoutId;
return function ( ... args ) {
clearTimeout (timeoutId);
timeoutId = setTimeout (() => func. apply ( this , args), delay);
};
}
const expensiveOperation = debounce (() => {
// Heavy computation
}, 300 );
// 7. Throttle scroll/resize handlers
function throttle ( func , limit ) {
let inThrottle;
return function ( ... args ) {
if ( ! inThrottle) {
func. apply ( this , args);
inThrottle = true ;
setTimeout (() => inThrottle = false , limit);
}
};
}
window. addEventListener ( 'scroll' , throttle (() => {
// Handle scroll
}, 100 ));
// 8. Web Workers for heavy computation
// main.js
const worker = new Worker ( 'worker.js' );
worker. postMessage ({ data: largeDataset });
worker. onmessage = ( e ) => {
console. log ( 'Result:' , e.data);
};
// worker.js
self. onmessage = ( e ) => {
const result = heavyComputation (e.data);
self. postMessage (result);
};
CSS Optimization
/* 1. Critical CSS (inline above-the-fold styles) */
/* Extract and inline critical CSS in <head> */
/* 2. Remove unused CSS */
/* Use PurgeCSS or similar tools */
/* 3. Minify CSS */
/* Use cssnano or similar */
/* 4. Use efficient selectors */
/* Good */
.button { }
/* Bad (slow) */
div > ul > li > a { }
/* 5. Avoid @import (blocks rendering) */
/* Bad */
@import url ( 'styles.css' );
/* Good */
/* <link rel="stylesheet" href="styles.css"> */
/* 6. Use CSS containment */
.component {
contain : layout style paint;
}
/* 7. Use content-visibility for off-screen content */
.list-item {
content-visibility : auto ;
contain-intrinsic-size : 0 500 px ;
}
Font Optimization
<!-- 1. Preload critical fonts -->
< link rel = "preload" href = "/fonts/main.woff2" as = "font" type = "font/woff2" crossorigin >
<!-- 2. Use font-display for better loading -->
< style >
@font-face {
font-family : 'CustomFont' ;
src : url ( '/fonts/custom.woff2' ) format ( 'woff2' );
font-display : swap ; /* Show fallback immediately, swap when loaded */
/* Options: auto, block, swap, fallback, optional */
}
</ style >
<!-- 3. Subset fonts (only include needed characters) -->
<!-- Use tools like glyphhanger or fonttools -->
<!-- 4. Use variable fonts (one file for all weights) -->
< style >
@font-face {
font-family : 'Inter' ;
src : url ( '/fonts/inter-variable.woff2' ) format ( 'woff2' );
font-weight : 100 900 ; /* Supports all weights */
}
</ style >
Compression
// Server-side compression (Node.js/Express)
const compression = require ( 'compression' );
app. use ( compression ({
level: 6 , // Compression level (0-9)
threshold: 1024 , // Only compress > 1KB
filter : ( req , res ) => {
// Don't compress images
if (req.headers[ 'x-no-compression' ]) {
return false ;
}
return compression. filter (req, res);
}
}));
// Brotli compression (better than gzip)
const shrinkRay = require ( 'shrink-ray-current' );
app. use ( shrinkRay ());
// Static file compression
// Pre-compress files at build time
// Serve .gz or .br files if available
app. get ( '*.js' , ( req , res , next ) => {
if (req. header ( 'Accept-Encoding' ). includes ( 'br' )) {
req.url = req.url + '.br' ;
res. set ( 'Content-Encoding' , 'br' );
res. set ( 'Content-Type' , 'application/javascript' );
} else if (req. header ( 'Accept-Encoding' ). includes ( 'gzip' )) {
req.url = req.url + '.gz' ;
res. set ( 'Content-Encoding' , 'gzip' );
res. set ( 'Content-Type' , 'application/javascript' );
}
next ();
});