Web Performance Optimization Cheatsheet

A comprehensive guide to web performance optimization - techniques for faster loading, rendering, and user interaction

Performance Metrics

Core Web Vitals

Google's key user experience metrics

# Largest Contentful Paint (LCP)
- Measures loading performance
- Good: <= 2.5 seconds
- Needs Improvement: <= 4 seconds
- Poor: > 4 seconds
- Affected by: server response time, render-blocking resources, resource load time, client-side rendering

# First Input Delay (FID)
- Measures interactivity
- Good: <= 100ms
- Needs Improvement: <= 300ms
- Poor: > 300ms
- Affected by: heavy JavaScript execution, long tasks, large JS bundles

# Cumulative Layout Shift (CLS)
- Measures visual stability
- Good: <= 0.1
- Needs Improvement: <= 0.25
- Poor: > 0.25
- Affected by: images without dimensions, ads, embeds, iframes, dynamically injected content

# Interaction to Next Paint (INP) - Replacing FID in March 2024
- Measures responsiveness
- Good: <= 200ms
- Needs Improvement: <= 500ms
- Poor: > 500ms
- Measured throughout page lifecycle (not just first interaction)

Other Important Metrics

Additional metrics for comprehensive performance evaluation

# Time to First Byte (TTFB)
- Time from request to first byte received
- Good: < 200ms
- Affected by: server processing, redirects, DNS lookup, connection setup

# First Contentful Paint (FCP)
- When first content appears on screen
- Good: <= 1.8 seconds
- Needs Improvement: <= 3 seconds
- Poor: > 3 seconds

# Total Blocking Time (TBT)
- Sum of all time between FCP and TTI where main thread was blocked
- Strong correlation with FID
- Good: < 200ms

# Time to Interactive (TTI)
- When page is fully interactive
- Good: <= 3.8 seconds
- Affected by: JavaScript size and execution time

# Speed Index
- How quickly content is visually displayed
- Good: <= 3.4 seconds
- Measures visual progression of page load

Measuring Performance

Tools and techniques for performance measurement

# Chrome DevTools Performance Panel
- Record runtime performance
- View main thread activity
- Identify bottlenecks
- Analyze rendering performance
- Access: F12 > Performance tab

# Lighthouse
- Open-source automated tool
- Audits performance, accessibility, SEO, etc.
- Access: Chrome DevTools > Lighthouse tab
- CLI: npm install -g lighthouse && lighthouse https://example.com

# WebPageTest.org
- Free performance testing tool
- Testing from multiple locations
- Waterfall charts, video, content breakdown
- Competitive analysis

# Chrome User Experience Report (CrUX)
- Real-user measurement (RUM) dataset
- Access via:
  - PageSpeed Insights
  - Search Console
  - BigQuery
  - CrUX API

# Performance API (JavaScript)
const perfEntries = performance.getEntriesByType('navigation');
const metrics = {
  TTFB: perfEntries[0].responseStart - perfEntries[0].requestStart,
  domLoad: perfEntries[0].domContentLoadedEventEnd,
  fullPageLoad: perfEntries[0].loadEventEnd
};

# web-vitals.js library
import {getLCP, getFID, getCLS} from 'web-vitals';

getCLS(console.log);  // CLS
getFID(console.log);  // FID
getLCP(console.log);  // LCP

Frontend Optimization

JavaScript Optimization

Techniques for optimizing JavaScript

# Code Splitting
// Before: One large bundle
import {bigFeature1, bigFeature2} from './big-module';

// After: Dynamic imports
const bigFeature1 = () => import('./big-feature-1');
const bigFeature2 = () => import('./big-feature-2');

# Tree Shaking
// Enable in webpack
// webpack.config.js
module.exports = {
  mode: 'production', // Enables tree shaking
  optimization: {
    usedExports: true
  }
};

# Avoid Render-Blocking JS
// Bad: Blocks parsing
<script src="app.js"></script>

// Better: Doesn't block parsing
<script src="app.js" defer></script>

// For critical JS: Loads in parallel and executes immediately
<script src="critical.js" async></script>

# Optimize Third-Party Scripts
// Load non-critical scripts after page load
window.addEventListener('load', () => {
  const script = document.createElement('script');
  script.src = 'https://third-party.com/widget.js';
  document.body.appendChild(script);
});

# Web Workers for CPU-intensive tasks
// main.js
const worker = new Worker('worker.js');
worker.postMessage({data: complexData});
worker.onmessage = (e) => {
  console.log('Result:', e.data);
};

// worker.js
self.onmessage = (e) => {
  const result = performComplexCalculation(e.data);
  self.postMessage(result);
};

CSS Optimization

Strategies for CSS performance

# Critical CSS
// Inline critical CSS in <head>
<style>
  /* Critical styles needed for above-the-fold content */
  .header, .hero { /* styles */ }
</style>

// Load full CSS asynchronously
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>

# Optimize CSS Selectors
// Inefficient (browsers read right-to-left)
body div header ul li a { color: red; }

// More efficient
.nav-link { color: red; }

# Minimize Repaints and Reflows
// Triggers reflow (bad for performance)
element.style.width = '100px'; // Read
console.log(element.offsetHeight); // Read - layout calculation
element.style.marginLeft = '10px'; // Write - causes reflow

// Better: Batch reads and writes
// Reads
const width = element.offsetWidth;
const height = element.offsetHeight;

// Then writes
element.style.width = '100px';
element.style.marginLeft = '10px';

# Use CSS Containment
.component {
  contain: content; /* Isolates layout, style, paint containment */
}

# Reduce Unused CSS
// Use tools like PurgeCSS
// postcss.config.js
module.exports = {
  plugins: [
    require('@fullhuman/postcss-purgecss')({
      content: ['./src/**/*.html', './src/**/*.js'],
    })
  ]
}

Image Optimization

Best practices for image performance

# Modern Image Formats
- WebP: 25-35% smaller than JPEG with same quality
- AVIF: 50% smaller than JPEG with same quality

# Responsive Images
<picture>
  <source srcset="image-large.webp 1200w, image-medium.webp 800w, image-small.webp 400w" 
          sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
          type="image/webp">
  <source srcset="image-large.jpg 1200w, image-medium.jpg 800w, image-small.jpg 400w" 
          sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
          type="image/jpeg">
  <img src="image-fallback.jpg" alt="Description" loading="lazy" width="800" height="600">
</picture>

# Lazy Loading
<!-- Native lazy loading -->
<img src="image.jpg" loading="lazy" alt="Description">

<!-- Intersection Observer API -->
<script>
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        observer.unobserve(img);
      }
    });
  });
  
  document.querySelectorAll('img[data-src]').forEach(img => {
    observer.observe(img);
  });
</script>

# Image CDN Services
// Examples: Cloudinary, imgix, Cloudflare Images
// Original URL
https://example.com/images/photo.jpg

// CDN URL with transformations
https://example.cloudinary.com/f_auto,q_auto,w_800/photo.jpg

# Always Set Width & Height Attributes
<img src="image.jpg" width="800" height="600" alt="Description">
/* Prevents layout shifts when loading */

Backend & Network Optimization

Server Optimization

Techniques for server-side performance

# HTTP/2 Features
- Multiplexing: Multiple requests over one connection
- Server Push: Send resources before they're requested
- Header Compression: Reduces overhead
- Binary Protocol: More efficient than text

# Enable HTTP/2 on Nginx
server {
    listen 443 ssl http2;
    # SSL configuration
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    # ...
}

# Enable HTTP/2 on Apache
<VirtualHost *:443>
    Protocols h2 http/1.1
    # SSL configuration
    SSLEngine on
    SSLCertificateFile /path/to/cert.pem
    SSLCertificateKeyFile /path/to/key.pem
    # ...
</VirtualHost>

# Server Push (HTTP/2)
// In Node.js
app.get('/', (req, res) => {
  const stream = res.push('/app.js', {
    request: { accept: '**/*' },
    response: { 'content-type': 'application/javascript' }
  });
  stream.end('console.log("pushed!");');
  res.end('<html><script src="/app.js"></script></html>');
});

# Server-Side Caching
// Redis caching example (pseudocode)
// Initialize Redis client
const client = createRedisClient();

// Store data with expiration (1 hour)
client.set("key", "value", "EX", 3600);

// Get cached data
const value = client.get("key");

// Caching pattern for database queries
function getUserWithCache(id) {
  // Check cache first
  const cachedUser = client.get("user:" + id);
  if (cachedUser) return JSON.parse(cachedUser);
  
  // Get from database if not in cache
  const user = database.getUser(id);
  
  // Save in cache for future requests
  client.set("user:" + id, JSON.stringify(user), "EX", 3600);
  
  return user;
}

Caching Strategies

Effective caching techniques for web performance

# Browser Cache Headers
# Cache-Control (modern)
Cache-Control: max-age=31536000, immutable # For versioned resources
Cache-Control: no-cache # Force revalidation
Cache-Control: private, max-age=3600 # Private with expiration
Cache-Control: public, max-age=86400 # Shared cache, 1 day

# ETag for validation
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

# Last-Modified for validation
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

# Service Worker Caching
// service-worker.js
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) => {
      return cache.addAll([
        '/',
        '/index.html',
        '/styles.css',
        '/app.js',
        '/logo.png'
      ]);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

# CDN Caching
// Cloudflare example - Page Rule settings
URL: example.com/*
Cache Level: Cache Everything
Edge Cache TTL: 1 month
Browser Cache TTL: 1 month

Resource Hints

Optimize resource loading with browser hints

# Preload: Highest priority, load ASAP
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="important.js" as="script">
<link rel="preload" href="hero.webp" as="image">
<link rel="preload" href="font.woff2" as="font" crossorigin>

# Prefetch: Lower priority, for future pages
<link rel="prefetch" href="/next-page.html">
<link rel="prefetch" href="/next-page.js">

# Preconnect: Establish early connections
<link rel="preconnect" href="https://cdn.example.com">

# DNS-Prefetch: Resolve DNS early
<link rel="dns-prefetch" href="https://api.example.com">

# Prerender: Full preload of a page (use carefully)
<link rel="prerender" href="https://example.com/next-likely-page">

# Prioritizing Critical Resources
<!-- Highest priority -->
<link rel="preload" href="critical.css" as="style">
<link rel="stylesheet" href="critical.css">

<!-- Medium priority -->
<link rel="preconnect" href="https://third-party.com">

<!-- Lower priority -->
<link rel="prefetch" href="next-page.js">

User Perception Techniques

Perceived Performance

Making your app feel faster to users

# Skeleton Screens
<div class="skeleton-card">
  <div class="skeleton-image"></div>
  <div class="skeleton-title"></div>
  <div class="skeleton-text"></div>
</div>

.skeleton-card {
  padding: 20px;
  border-radius: 4px;
  background: #fff;
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.skeleton-image {
  height: 200px;
  background: linear-gradient(90deg, #f0f0f0, #e0e0e0, #f0f0f0);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
}
@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

# Progressive Rendering
<!-- First, load minimal critical HTML -->
<!DOCTYPE html>
<html>
<head>
  <style>/* Critical CSS */</style>
</head>
<body>
  <header><!-- Critical content --></header>
  <main id="content">Loading...</main>
  
  <script>
    // Then, fetch and render the rest
    fetch('/api/content')
      .then(res => res.json())
      .then(data => {
        document.getElementById('content').innerHTML = 
          renderContent(data);
      });
  </script>
</body>
</html>

# Optimistic UI
// In a React app
function TodoApp() {
  const [todos, setTodos] = useState([]);
  
  const addTodo = async (text) => {
    // Optimistically add to UI
    const newTodo = { id: Date.now(), text, completed: false };
    setTodos([...todos, newTodo]);
    
    try {
      // Actually save to server
      const savedTodo = await api.createTodo(text);
      // Update with real ID from server
      setTodos(todos => 
        todos.map(t => t.id === newTodo.id ? savedTodo : t)
      );
    } catch (error) {
      // Revert on error
      setTodos(todos => todos.filter(t => t.id !== newTodo.id));
      alert('Failed to add todo');
    }
  };
  
  // ...
}

Progressive Web App (PWA)

Make your web app installable and offline-capable

# Web App Manifest
// manifest.json
{
  "name": "My PWA App",
  "short_name": "PWA",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#4285f4",
  "icons": [
    {
      "src": "icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

# Link manifest in HTML
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#4285f4">

# Basic Service Worker for Offline
// service-worker.js
const CACHE_NAME = 'my-pwa-cache-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/offline.html'
];

// Install and cache
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});

// Serve from cache, fall back to network
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // Cache hit - return response
        if (response) return response;
        
        // Try network
        return fetch(event.request)
          .then(response => {
            // Don't cache if not valid response
            if (!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }
            
            // Cache the new resource
            const responseToCache = response.clone();
            caches.open(CACHE_NAME)
              .then(cache => {
                cache.put(event.request, responseToCache);
              });
            
            return response;
          })
          .catch(() => {
            // Offline fallback
            if (event.request.mode === 'navigate') {
              return caches.match('/offline.html');
            }
          });
      })
  );
});

# Register Service Worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
      .then(registration => {
        console.log('SW registered:', registration.scope);
      })
      .catch(err => {
        console.log('SW registration failed:', err);
      });
  });
}

Performance Budgets

Setting and enforcing performance limits

# Types of Performance Budgets
- Quantity-based: Number of resources, file size
- Rule-based: Lighthouse scores, Core Web Vitals thresholds
- Time-based: Time to specific events (FCP, TTI)

# Example Budget
{
  "resourceSizes": [
    {
      "resourceType": "script",
      "budget": 170 // kB
    },
    {
      "resourceType": "total",
      "budget": 500 // kB
    }
  ],
  "timings": [
    {
      "metric": "largest-contentful-paint",
      "budget": 2500 // ms
    },
    {
      "metric": "cumulative-layout-shift",
      "budget": 0.1
    }
  ]
}

# Webpack Performance Budget
// webpack.config.js
module.exports = {
  performance: {
    maxAssetSize: 100000, // 100 kB
    maxEntrypointSize: 250000, // 250 kB
    hints: 'error' // 'warning' or false are other options
  }
};

# Automated CI Testing
// Example Lighthouse CI configuration
// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: ['https://example.com/'],
      numberOfRuns: 3
    },
    assert: {
      assertions: {
        'categories:performance': ['error', {minScore: 0.9}],
        'largest-contentful-paint': ['error', {maxNumericValue: 2500}],
        'cumulative-layout-shift': ['error', {maxNumericValue: 0.1}]
      }
    },
    upload: {
      target: 'temporary-public-storage'
    }
  }
};