Web Performance Basics

Week 4: Web Fundamentals - Friday Session

Introduction to Web Performance

Web performance is like the difference between a sports car and a compact sedan. Both will get you from point A to point B, but the sports car provides a smoother, faster, and more enjoyable experience. Similarly, a high-performing website delivers content quickly and responds promptly to user interactions, creating a satisfying experience that keeps users engaged.

In today's world, users expect websites to load almost instantly. Studies show that 53% of mobile site visits are abandoned if pages take longer than 3 seconds to load. Performance isn't just about user satisfaction—it directly impacts business metrics like conversion rates, bounce rates, and even search engine rankings.

By the end of this session, you'll understand:

  • Why web performance matters from business and user experience perspectives
  • Key performance metrics and how to measure them
  • Common performance bottlenecks and how to identify them
  • Practical techniques to optimize frontend performance
  • Tools and methodologies for ongoing performance monitoring

The Restaurant Analogy

Think of a website like a restaurant. The performance of a website is similar to the service quality at a restaurant:

  • Load time is like waiting to be seated - if it takes too long, customers might leave
  • Rendering is like seeing your food arrive at the table - the visual confirmation that your order is being processed
  • Time to Interactive is like having the food actually ready to eat - it's there, but can you interact with it yet?
  • Optimization techniques are like the efficiency systems in the kitchen - they're not visible to customers but make everything run smoothly

Why Performance Matters

Impact on User Experience

Performance directly affects how users perceive your website or application. Here's how:

  • Attention Span: Users have increasingly short attention spans. A delay of just a few seconds can cause frustration and abandonment.
  • Perceived Quality: Performance issues are often equated with poor quality and low trustworthiness. A fast site feels professional and reliable.
  • Accessibility: Not all users have high-speed connections or powerful devices. Performance optimizations make your site more accessible to a broader audience.
  • User Satisfaction: Fast, responsive interfaces create a positive emotional response, leading to a better overall impression of your brand.

Real-World Impact

  • Pinterest reduced perceived wait times by 40% and saw a 15% increase in SEO traffic and sign-ups
  • BBC found they lose an additional 10% of users for every additional second their site takes to load
  • Walmart observed a 2% conversion increase for every 1 second of improvement in page load time

Business Impact

Beyond user experience, performance has tangible business effects:

  • Conversion Rates: Faster sites convert better. Every 100ms of improvement in load time can increase conversion rates.
  • Bounce Rates: Slow sites see higher bounce rates as users leave before engaging with content.
  • SEO Rankings: Page speed is a ranking factor for search engines like Google.
  • Operating Costs: Efficient sites consume less bandwidth and server resources, potentially reducing hosting costs.

Typical Performance Impact on Business Metrics

Performance Improvement Typical Business Impact
1 second faster 7% increase in conversions
50% faster load time 12% increase in conversions
30% faster site 2-3x higher engagement rate

Mobile Performance Considerations

With mobile traffic now exceeding desktop for many websites, mobile performance deserves special attention:

  • Network Constraints: Mobile networks are often slower and less reliable than desktop connections.
  • Device Limitations: Mobile devices typically have less processing power and memory than desktops.
  • Context Sensitivity: Mobile users are often on the go and may be more sensitive to delays.
  • Battery Impact: Inefficient websites can drain device batteries faster, creating a negative impression.

Detecting Network Conditions

// Using the Network Information API to adapt to network conditions
if (navigator.connection) {
  const connection = navigator.connection;
  
  if (connection.saveData) {
    // User has requested reduced data usage
    loadLowResolutionImages();
  } else if (connection.effectiveType === '4g') {
    // Fast connection, load high-quality assets
    loadHighResolutionImages();
  } else {
    // Slower connection, load optimized assets
    loadOptimizedImages();
  }
}

Measuring Performance

Before you can improve performance, you need to measure it. Understanding key metrics and how to track them is essential for effective optimization.

Core Web Vitals and Other Key Metrics

Largest Contentful Paint (LCP)

Measures loading performance. To provide a good user experience, LCP should occur within 2.5 seconds of when the page first starts loading.

Imagine a newspaper gradually appearing on your doorstep. LCP is the moment when the main headline and featured image become visible.

First Input Delay (FID)

Measures interactivity. To provide a good user experience, pages should have a FID of 100 milliseconds or less.

Think of this as how long it takes a waiter to acknowledge you after you've raised your hand to order. If they take too long, you feel ignored and frustrated.

Cumulative Layout Shift (CLS)

Measures visual stability. To provide a good user experience, pages should maintain a CLS of 0.1 or less.

Imagine reading a physical book and the text and images suddenly jump around as you're trying to read. That's what a high CLS feels like to users.

Time to First Byte (TTFB)

Measures how long it takes for the browser to receive the first byte of response from the server.

Like waiting for the first word of an answer after asking a question. Even if the full answer takes time, hearing that first word quickly tells you someone's responding.

First Contentful Paint (FCP)

Measures how long it takes for the browser to render the first piece of DOM content.

Similar to seeing the first brush stroke appear on a previously blank canvas. It confirms that something is happening.

Time to Interactive (TTI)

Measures how long it takes for the page to become fully interactive.

Imagine a theme park ride that's visible but has a "Not Yet Operating" sign. TTI is when the ride is actually available for you to use.

Tools for Measuring Performance

Browser Developer Tools

Built-in tools in browsers like Chrome, Firefox, and Safari that provide detailed performance insights.

  • Network Panel: Visualizes resource loading, timing, and size
  • Performance Panel: Records and analyzes runtime performance
  • Memory Panel: Identifies memory usage and leaks
Quick Tip: Using Chrome's Performance Panel
  1. Open DevTools (F12 or Right-click > Inspect)
  2. Go to the Performance tab
  3. Click the record button (circle)
  4. Interact with the page
  5. Stop recording to see detailed performance analysis

Lighthouse

An open-source, automated tool for improving the quality of web pages. It runs audits for performance, accessibility, progressive web apps, SEO, and more.

Using Lighthouse
  1. In Chrome DevTools, go to the Lighthouse tab
  2. Select the categories you want to audit
  3. Click "Generate report"
  4. Review the results and recommendations

WebPageTest

A free tool that allows you to test your website's performance from multiple locations around the world using real browsers.

WebPageTest API Example
// Using the WebPageTest API with Node.js
const WebPageTest = require('webpagetest');
const wpt = new WebPageTest('www.webpagetest.org', 'YOUR_API_KEY');

wpt.runTest('https://example.com', {
  location: 'Sydney:Chrome',
  connectivity: '3G',
  runs: 3,
  firstViewOnly: false
}, (err, data) => {
  if (err) {
    console.error('Error running test:', err);
    return;
  }
  
  console.log('Test results:', data);
});

Google PageSpeed Insights

Analyzes the content of a web page and generates suggestions to make that page faster based on Google's PageSpeed rules.

Real User Monitoring (RUM)

Collects performance data from actual users as they interact with your site.

Using the Performance API for Basic RUM
// Basic Real User Monitoring using the Performance API
window.addEventListener('load', () => {
  // Wait for the page to fully load to get accurate metrics
  setTimeout(() => {
    const perfData = window.performance.timing;
    
    // Calculate key metrics
    const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
    const domContentLoaded = perfData.domContentLoadedEventEnd - perfData.navigationStart;
    
    // Send the data to your analytics server
    navigator.sendBeacon('/analytics', JSON.stringify({
      pageLoadTime,
      domContentLoaded,
      url: document.location.href,
      timestamp: new Date().toISOString()
    }));
  }, 0);
});

Setting Performance Budgets

A performance budget is a set of limits on metrics that affect site performance. It's like a financial budget, but for user experience instead of money.

Types of Performance Budgets

  • Quantity-based: Limits on the number of HTTP requests, total weight of a page, etc.
  • Rule-based: Scores from tools like Lighthouse or WebPageTest
  • Timing-based: Limits on metrics like TTI, LCP, etc.

Example Performance Budget

{
  "resources": {
    "total": 320,           // Total weight in KB
    "script": 120,          // JS weight in KB
    "image": 140,           // Images weight in KB
    "stylesheet": 40,       // CSS weight in KB
    "document": 20,         // HTML weight in KB
    "font": 20,             // Fonts weight in KB
    "thirdParty": 100       // Third-party scripts in KB
  },
  "requests": {
    "total": 30,            // Maximum number of requests
    "script": 8,            // JS requests
    "image": 12,            // Image requests
    "stylesheet": 2,        // CSS requests
    "font": 2,              // Font requests
    "thirdParty": 6         // Third-party requests
  },
  "metrics": {
    "LCP": 2500,            // Largest Contentful Paint in ms
    "FID": 100,             // First Input Delay in ms
    "CLS": 0.1,             // Cumulative Layout Shift score
    "TTI": 5000             // Time to Interactive in ms
  }
}

Tools for Enforcing Performance Budgets

  • Lighthouse CI: Integrates into CI/CD pipelines to ensure performance metrics stay within budget
  • Webpack Bundle Analyzer: Visualizes bundle size and helps identify large dependencies
  • bundlesize: Checks if your bundle size exceeds a specified threshold
Setting up bundlesize in package.json
{
  "bundlesize": [
    {
      "path": "./dist/js/*.js",
      "maxSize": "120 kB"
    },
    {
      "path": "./dist/css/*.css",
      "maxSize": "40 kB"
    }
  ]
}

Common Performance Bottlenecks

Understanding common performance issues is crucial for effective optimization. Here are the main areas where bottlenecks typically occur:

Network and Asset Loading

Network-related issues are often the most significant contributors to slow page loads.

Large File Sizes

Oversized images, uncompressed assets, and bloated code all contribute to slow download times.

Solutions:
  • Optimize and compress images
  • Minify CSS and JavaScript
  • Use modern image formats (WebP, AVIF)
  • Implement code splitting
Image Optimization with Modern Formats
<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="Description" loading="lazy">
</picture>

Too Many Requests

Each HTTP request adds latency, especially on high-latency connections.

Solutions:
  • Bundle files appropriately
  • Use CSS sprites for small images
  • Inline critical CSS
  • Implement domain sharding for concurrent downloads
  • Use HTTP/2 or HTTP/3 to reduce the impact of multiple requests
Inlining Critical CSS
<!-- Inline critical CSS in the head -->
<head>
  <style>
    /* Critical CSS for above-the-fold content */
    body { margin: 0; font-family: sans-serif; }
    header { background: #f5f5f5; padding: 1rem; }
    .hero { height: 50vh; background: linear-gradient(45deg, #1a2a6c, #b21f1f); color: white; display: flex; align-items: center; justify-content: center; }
  </style>
  
  <!-- Load non-critical 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>
</head>

Render-Blocking Resources

CSS and JavaScript that block page rendering until they're downloaded and processed.

Solutions:
  • Load non-critical JavaScript asynchronously or defer it
  • Inline critical CSS
  • Prioritize above-the-fold content
Async and Defer Scripts
<!-- Regular script: blocks parsing -->
<script src="blocking.js"></script>

<!-- Async script: downloads in parallel, executes as soon as available -->
<script src="analytics.js" async></script>

<!-- Deferred script: downloads in parallel, executes after parsing -->
<script src="non-critical.js" defer></script>

Inefficient Resource Loading

Resources that aren't loaded in the optimal order or with the right priorities.

Solutions:
  • Use resource hints (preload, prefetch, preconnect)
  • Implement server push with HTTP/2
  • Prioritize critical resources
Resource Hints
<!-- Preconnect to required origins -->
<link rel="preconnect" href="https://api.example.com">

<!-- Preload critical resources -->
<link rel="preload" href="critical-font.woff2" as="font" type="font/woff2" crossorigin>

<!-- Prefetch resources for the next page -->
<link rel="prefetch" href="next-page.html">

Rendering Performance

Once resources are loaded, how efficiently they're processed and rendered affects perceived performance.

Layout Thrashing

Causes the browser to recalculate layout multiple times unnecessarily.

Solutions:
  • Batch DOM reads and writes
  • Use CSS transforms instead of properties that trigger layout
  • Avoid forcing synchronous layouts
Avoiding Layout Thrashing
// Bad practice - causes layout thrashing
function badLayout() {
  const elements = document.querySelectorAll('.box');
  
  elements.forEach(el => {
    // Read (forces layout calculation)
    const height = el.offsetHeight;
    
    // Write (invalidates layout)
    el.style.height = (height + 10) + 'px';
    
    // Read again (forces another layout calculation)
    const width = el.offsetWidth;
    
    // Write again (invalidates layout again)
    el.style.width = (width + 10) + 'px';
  });
}

// Good practice - batch reads and writes
function goodLayout() {
  const elements = document.querySelectorAll('.box');
  const measurements = [];
  
  // Batch all reads
  elements.forEach(el => {
    measurements.push({
      height: el.offsetHeight,
      width: el.offsetWidth
    });
  });
  
  // Batch all writes
  elements.forEach((el, i) => {
    const { height, width } = measurements[i];
    el.style.height = (height + 10) + 'px';
    el.style.width = (width + 10) + 'px';
  });
}

Expensive Styles and Animations

CSS properties and animations that are computationally expensive.

Solutions:
  • Use CSS properties that only affect compositing (transform, opacity)
  • Avoid expensive properties (box-shadow, filter, etc.) in animations
  • Use will-change for elements that will animate (but use sparingly)
  • Reduce paint areas
Performant Animations
/* Expensive animation (triggers layout and paint) */
.expensive {
  animation: move-expensive 1s infinite;
}

@keyframes move-expensive {
  from { left: 0; top: 0; }
  to { left: 100px; top: 100px; }
}

/* Performant animation (only affects compositing) */
.performant {
  animation: move-performant 1s infinite;
}

@keyframes move-performant {
  from { transform: translate(0, 0); }
  to { transform: translate(100px, 100px); }
}

Layout Shifts

Elements that change position after they're initially rendered, causing a jarring experience for users.

Solutions:
  • Set size attributes on images and other media
  • Reserve space for dynamic content like ads
  • Avoid inserting content above existing content
  • Use CSS containment where appropriate
Preventing Layout Shifts
<!-- Bad: image without dimensions -->
<img src="image.jpg" alt="Description">

<!-- Good: image with aspect ratio -->
<div style="aspect-ratio: 16/9; max-width: 800px;">
  <img src="image.jpg" alt="Description" style="width: 100%; height: 100%; object-fit: cover;">
</div>

<!-- Good: reserved space for dynamic content -->
<div class="ad-container" style="min-height: 250px;">
  <!-- Ad will load here -->
</div>

JavaScript Performance

JavaScript execution can significantly impact the responsiveness of your web application.

Long-Running JavaScript

JavaScript that blocks the main thread for extended periods, causing unresponsiveness.

Solutions:
  • Break up long tasks into smaller chunks
  • Defer non-critical work
  • Use Web Workers for CPU-intensive tasks
  • Implement virtualization for long lists
Using Web Workers for CPU-Intensive Tasks
// In main.js
const worker = new Worker('processor.js');

// Send data to the worker
worker.postMessage({
  data: largeDataSet,
  config: processingConfig
});

// Handle the result
worker.onmessage = function(event) {
  const result = event.data;
  updateUI(result);
};

// In processor.js (the worker)
self.onmessage = function(event) {
  const { data, config } = event.data;
  
  // Perform CPU-intensive processing
  const result = processData(data, config);
  
  // Send the result back to the main thread
  self.postMessage(result);
};

function processData(data, config) {
  // Computationally expensive operation
  // that doesn't block the main thread
  return data.map(/* ... */);
}

Inefficient DOM Manipulation

Frequent or poorly optimized interactions with the DOM.

Solutions:
  • Minimize DOM updates
  • Use DocumentFragment for batch insertions
  • Consider using a virtual DOM library for complex UIs
  • Remove event listeners when no longer needed
Efficient DOM Updates
// Inefficient - causes multiple reflows
function addItemsInefficiently(items) {
  const list = document.getElementById('list');
  
  items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    list.appendChild(li); // Causes reflow each time
  });
}

// Efficient - uses DocumentFragment for batch update
function addItemsEfficiently(items) {
  const list = document.getElementById('list');
  const fragment = document.createDocumentFragment();
  
  items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    fragment.appendChild(li);
  });
  
  list.appendChild(fragment); // Single reflow
}

Memory Leaks

JavaScript that continues to consume memory, eventually causing performance degradation or crashes.

Solutions:
  • Remove event listeners when elements are removed
  • Watch for circular references in objects
  • Be cautious with closures that capture large objects
  • Use Chrome DevTools' Memory panel to identify leaks
Preventing Memory Leaks
// Memory leak example
function setupLeakyHandler() {
  const largeData = new Array(1000000).fill('potentially large data');
  
  const element = document.getElementById('button');
  
  // This creates a closure that captures largeData
  element.addEventListener('click', function() {
    console.log(largeData.length);
  });
  
  // Even if element is removed from DOM, the event listener
  // keeps largeData in memory
}

// Fixed example
function setupEfficientHandler() {
  const element = document.getElementById('button');
  
  // Reference only what you need
  element.addEventListener('click', processClick);
}

function processClick() {
  // Generate data only when needed
  const data = generateData();
  console.log(data.length);
}

// Clean up when no longer needed
function cleanup() {
  const element = document.getElementById('button');
  element.removeEventListener('click', processClick);
}

Key Optimization Techniques

Now that we understand common bottlenecks, let's explore practical optimization techniques. These strategies will help you improve the performance of your web applications.

Asset Optimization

Image Optimization

  • Choose the right format: JPEG for photos, PNG for transparency, SVG for vector graphics, WebP/AVIF for modern browsers
  • Responsive images: Use srcset and sizes attributes to serve different image sizes based on viewport
  • Lazy loading: Only load images when they're close to the viewport
  • Modern compression: Use tools like sharp, imagemin, or squoosh
Responsive and Lazy Loaded Images
<img 
  src="small.jpg" 
  srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1600w" 
  sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1600px"
  alt="Responsive image"
  loading="lazy"
  decoding="async"
  width="800"
  height="600"
>

CSS Optimization

  • Minification: Remove unnecessary whitespace, comments, and characters
  • Critical CSS: Inline critical styles to reduce render-blocking
  • Remove unused CSS: Tools like PurgeCSS can eliminate unused rules
  • CSS modularization: Only load the CSS needed for each page
Using PurgeCSS with a Build Process
// webpack.config.js with PurgeCSS (simplified)
const purgecss = require('@fullhuman/postcss-purgecss');

module.exports = {
  // ...webpack config...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  purgecss({
                    content: ['./src/**/*.html', './src/**/*.js'],
                    // Safelist patterns if needed
                    safelist: ['whitelisted-class', /^modal-/]
                  })
                ]
              }
            }
          }
        ]
      }
    ]
  }
};

JavaScript Optimization

  • Minification: Reduce file size by removing unnecessary characters
  • Code splitting: Break your JavaScript into smaller chunks
  • Tree shaking: Eliminate dead code
  • Differential loading: Serve modern JavaScript to modern browsers, and transpiled code to older browsers
Code Splitting with Dynamic Imports
// Without code splitting - loads everything upfront
import { heavyFeature } from './heavy-feature';

// With code splitting - loads on demand
document.getElementById('feature-button').addEventListener('click', async () => {
  try {
    // Dynamic import - loads the module only when needed
    const { heavyFeature } = await import('./heavy-feature');
    heavyFeature();
  } catch (error) {
    console.error('Failed to load feature:', error);
  }
});

Font Optimization

  • WOFF2 format: Most efficient compression for web fonts
  • Font subsetting: Only include the characters you need
  • Font display strategies: Use font-display property to control rendering behavior
  • Preload critical fonts: Especially for key typographic elements
Optimized Font Loading
<!-- Preload the font file -->
<link rel="preload" href="fonts/my-font.woff2" as="font" type="font/woff2" crossorigin>

<style>
  /* Define the font with optimal settings */
  @font-face {
    font-family: 'MyFont';
    src: url('fonts/my-font.woff2') format('woff2'),
         url('fonts/my-font.woff') format('woff');
    font-weight: normal;
    font-style: normal;
    font-display: swap; /* Show fallback font until custom font is loaded */
  }
  
  /* Apply the font */
  body {
    font-family: 'MyFont', sans-serif;
  }
</style>

Delivery Optimization

Caching Strategies

  • Browser caching: Set appropriate cache headers for different resource types
  • Content fingerprinting: Include content hashes in filenames for cache busting
  • Service Workers: Implement offline caching for PWAs
  • Cache-Control policies: Configure fine-grained caching behavior
Setting Cache Headers in Various Environments
// Express.js (Node.js) example
app.use('/static', express.static('public', {
  maxAge: '1y',
  setHeaders: (res, path) => {
    if (path.endsWith('.html')) {
      // Don't cache HTML files
      res.setHeader('Cache-Control', 'no-cache');
    } else if (path.includes('critical.css')) {
      // Short cache for critical CSS
      res.setHeader('Cache-Control', 'public, max-age=3600');
    } else if (path.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg)$/)) {
      // Long cache for static assets
      res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
    }
  }
}));

// Nginx configuration
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
  expires 1y;
  add_header Cache-Control "public, max-age=31536000, immutable";
}

location ~* \.(html)$ {
  add_header Cache-Control "no-cache";
}

Content Delivery Networks (CDNs)

  • Distribute content: Serve assets from geographically distributed servers
  • Edge caching: Cache content at the network edge, closer to users
  • Advanced features: Many CDNs offer image optimization, minification, and more
  • HTTP/2 or HTTP/3 support: Benefit from modern protocol optimizations
Integrating with a CDN
// Configure your build tool to output to your CDN
// webpack.config.js example
module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? 'https://cdn.example.com/assets/'
      : '/'
  }
};

Compression

  • Gzip/Brotli: Enable HTTP compression for text-based resources
  • Precompression: Compress files at build time rather than on-demand
  • Appropriate compression levels: Balance compression ratio vs. CPU usage
Enabling Compression in Nginx
# Nginx configuration for Brotli and Gzip
# Enable Brotli
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/javascript application/json image/svg+xml application/xml+rss;

# Enable Gzip as fallback
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/javascript application/json image/svg+xml application/xml+rss;

HTTP/2 and HTTP/3

  • Multiplexing: Send multiple requests and responses in parallel over a single connection
  • Header compression: Reduce overhead of HTTP headers
  • Server push: Proactively send resources the client will need
  • HTTP/3: Uses QUIC protocol for reduced latency and improved connection migration
Setting Up HTTP/2 in Nginx
# Nginx configuration for HTTP/2
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    # HTTP/2 server push
    location / {
        http2_push /styles/main.css;
        http2_push /scripts/main.js;
    }
}

Runtime Optimization

Debouncing and Throttling

  • Debouncing: Group multiple sequential calls into a single one after a period of inactivity
  • Throttling: Limit the number of times a function can be called over time
  • Common use cases: Search inputs, scroll listeners, resize events, etc.
Implementing Debounce and Throttle
// Debounce function - executes after a delay of inactivity
function debounce(func, wait) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  };
}

// Throttle function - executes at most once per specified time period
function throttle(func, limit) {
  let lastCall = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastCall >= limit) {
      lastCall = now;
      func.apply(this, args);
    }
  };
}

// Usage examples
const debouncedSearch = debounce(function(query) {
  // Search implementation - called 300ms after user stops typing
  performSearch(query);
}, 300);

const throttledScroll = throttle(function() {
  // Scroll handler - called at most once every 100ms
  updateScrollIndicator();
}, 100);

// Event listeners
document.getElementById('search').addEventListener('input', function(e) {
  debouncedSearch(e.target.value);
});

window.addEventListener('scroll', throttledScroll);

Virtualization

  • Virtual scrolling/windowing: Only render visible items in long lists
  • Common libraries: react-window, vue-virtual-scroller, etc.
  • Use cases: Infinite scrolling lists, data tables, etc.
Basic Virtual List Implementation
// Simplified virtual list using vanilla JS
class VirtualList {
  constructor(container, items, itemHeight) {
    this.container = container;
    this.items = items;
    this.itemHeight = itemHeight;
    
    this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 2;
    this.scrollTop = 0;
    this.startIndex = 0;
    
    this.init();
  }
  
  init() {
    // Create a wrapper that will have the height of all items
    this.wrapper = document.createElement('div');
    this.wrapper.style.position = 'relative';
    this.wrapper.style.height = `${this.items.length * this.itemHeight}px`;
    this.container.appendChild(this.wrapper);
    
    // Handle scroll events
    this.container.addEventListener('scroll', this.handleScroll.bind(this));
    
    // Initial render
    this.render();
  }
  
  handleScroll() {
    this.scrollTop = this.container.scrollTop;
    this.render();
  }
  
  render() {
    // Calculate visible range
    this.startIndex = Math.floor(this.scrollTop / this.itemHeight);
    const endIndex = Math.min(
      this.startIndex + this.visibleItems, 
      this.items.length
    );
    
    // Clear current content
    this.wrapper.innerHTML = '';
    
    // Render only visible items
    for (let i = this.startIndex; i < endIndex; i++) {
      const itemEl = document.createElement('div');
      itemEl.style.position = 'absolute';
      itemEl.style.top = `${i * this.itemHeight}px`;
      itemEl.style.height = `${this.itemHeight}px`;
      itemEl.style.width = '100%';
      itemEl.textContent = this.items[i];
      this.wrapper.appendChild(itemEl);
    }
  }
}

// Usage
const container = document.getElementById('list-container');
const items = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
new VirtualList(container, items, 40);

Passive Event Listeners

  • Purpose: Indicate that a listener will not call preventDefault()
  • Benefit: Browser can optimize scrolling and other operations
  • Common use cases: Scroll, touchstart, touchmove listeners
Using Passive Event Listeners
// Without passive option - browser doesn't know if preventDefault() will be called
window.addEventListener('scroll', function(event) {
  // Scroll handler
});

// With passive option - browser knows preventDefault() won't be called
window.addEventListener('scroll', function(event) {
  // Same scroll handler
}, { passive: true });

// Feature detection for passive support
let passiveSupported = false;
try {
  const options = {
    get passive() {
      passiveSupported = true;
      return false;
    }
  };
  window.addEventListener('test', null, options);
  window.removeEventListener('test', null, options);
} catch (err) {
  passiveSupported = false;
}

// Use passive if supported
window.addEventListener('scroll', handleScroll, 
  passiveSupported ? { passive: true } : false);

Efficient Animations

  • Use requestAnimationFrame: Synchronize animations with the browser's refresh cycle
  • Promote to GPU: Use transform and opacity for hardware acceleration
  • Reduce paint area: Limit the region that needs repainting
  • Consider Web Animations API: More efficient than CSS animations in some cases
Efficient Animation with requestAnimationFrame
// Inefficient animation using setTimeout
function badAnimation() {
  let position = 0;
  const element = document.getElementById('moving-element');
  
  function update() {
    position += 2;
    element.style.left = position + 'px';
    
    if (position < 500) {
      setTimeout(update, 16); // ~60fps, but not synchronized with screen refresh
    }
  }
  
  update();
}

// Efficient animation using requestAnimationFrame
function goodAnimation() {
  let position = 0;
  const element = document.getElementById('moving-element');
  
  function update() {
    position += 2;
    // Use transform instead of left for GPU acceleration
    element.style.transform = `translateX(${position}px)`;
    
    if (position < 500) {
      requestAnimationFrame(update); // Synchronized with screen refresh
    }
  }
  
  requestAnimationFrame(update);
}

Ongoing Performance Monitoring

Web performance isn't a one-time effort—it requires ongoing monitoring and optimization. Here's how to set up a sustainable performance monitoring system.

Real User Monitoring (RUM)

Real User Monitoring collects performance data from actual users of your website, giving you insights into real-world conditions.

Implementing Basic RUM

Use the Performance API to collect key metrics from real users:

// Basic implementation of Real User Monitoring
window.addEventListener('load', () => {
  // Ensure all the resources are loaded before measuring
  setTimeout(() => {
    // Get performance entries
    const perfData = performance.getEntriesByType('navigation')[0];
    const paintEntries = performance.getEntriesByType('paint');
    
    // Extract key metrics
    const metrics = {
      // Navigation timing
      TTFB: perfData.responseStart - perfData.requestStart,
      
      // Page load time
      loadTime: perfData.loadEventEnd - perfData.startTime,
      
      // DOM content loaded
      domContentLoaded: perfData.domContentLoadedEventEnd - perfData.startTime,
      
      // First Paint
      firstPaint: getFirstPaint(paintEntries),
      
      // First Contentful Paint
      firstContentfulPaint: getFirstContentfulPaint(paintEntries),
      
      // Page URL and timestamp
      url: window.location.href,
      timestamp: new Date().toISOString()
    };
    
    // Send metrics to analytics endpoint
    sendMetricsToAnalytics(metrics);
  }, 0);
});

// Helper functions
function getFirstPaint(entries) {
  const fp = entries.find(entry => entry.name === 'first-paint');
  return fp ? fp.startTime : 0;
}

function getFirstContentfulPaint(entries) {
  const fcp = entries.find(entry => entry.name === 'first-contentful-paint');
  return fcp ? fcp.startTime : 0;
}

function sendMetricsToAnalytics(metrics) {
  // Use Navigator.sendBeacon for reliability
  navigator.sendBeacon('/analytics/performance', JSON.stringify(metrics));
}

Web Vitals Monitoring

Google's Web Vitals library makes it easy to collect Core Web Vitals metrics:

// Using the web-vitals library
import {getLCP, getFID, getCLS} from 'web-vitals';

function sendToAnalytics({name, delta, id}) {
  // Send metric to your analytics service
  const analyticsData = {
    metric: name,
    value: delta,
    id: id,
    page: window.location.pathname,
    timestamp: Date.now()
  };
  
  navigator.sendBeacon('/analytics/vitals', JSON.stringify(analyticsData));
}

// Monitor Core Web Vitals
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);

RUM Tools and Services

Several services provide comprehensive RUM capabilities:

  • Open source: Boomerang, Perfume.js
  • Commercial: New Relic, Datadog, Dynatrace, SpeedCurve
  • Analytics platforms: Google Analytics also provides some performance insights

Synthetic Testing

While RUM gives you data from real users, synthetic testing allows for consistent, controlled measurements over time.

Continuous Performance Testing

Integrate performance testing into your CI/CD pipeline:

Using Lighthouse CI
// .lighthouserc.js configuration
module.exports = {
  ci: {
    collect: {
      // Number of runs
      numberOfRuns: 3,
      
      // URLs to test
      url: ['https://example.com/', 'https://example.com/about'],
      
      // Chrome flags
      chromeFlags: '--headless',
      
      // Simulated throttling settings
      settings: {
        throttlingMethod: 'simulate',
        throttling: {
          cpuSlowdownMultiplier: 4,
          networkDownloadSpeed: 1638400,
          networkUploadSpeed: 675840,
          networkRTT: 150
        }
      }
    },
    assert: {
      // Performance budget assertions
      assertions: {
        'categories:performance': ['error', {minScore: 0.9}],
        'first-contentful-paint': ['error', {maxNumericValue: 2000}],
        'interactive': ['error', {maxNumericValue: 3500}],
        'largest-contentful-paint': ['error', {maxNumericValue: 2500}],
        'cumulative-layout-shift': ['error', {maxNumericValue: 0.1}],
        'max-potential-fid': ['error', {maxNumericValue: 100}]
      }
    },
    upload: {
      // Upload results to a Lighthouse CI server
      target: 'lhci',
      serverBaseUrl: 'https://your-lhci-server.example.com'
    }
  }
};

Scheduled Performance Tests

Regular tests help identify performance regressions over time:

  • WebPageTest API: Schedule regular tests from different locations
  • Lighthouse on a schedule: Run via cron jobs or scheduled workflows
  • Puppeteer/Playwright scripts: Custom performance measurement scripts

Performance Data Visualization

Visualizing performance data helps identify trends and communicate issues to stakeholders.

Performance Dashboards

Create dashboards to monitor key metrics over time:

  • Time-series graphs: Track metrics over time to identify trends and regressions
  • Heatmaps: Visualize distributions of performance across users
  • Geographic visualizations: Understand performance differences by region

Performance Alerts

Set up alerts for performance regressions:

  • Static thresholds: Alert when metrics exceed specific values
  • Statistical anomaly detection: Alert on unusual changes compared to historical data
  • Integration with communication tools: Send alerts to Slack, email, etc.

Building a Performance Culture

Technical solutions are only part of the equation. Creating a culture that values performance is equally important.

Performance Budgets

Establish and enforce performance budgets:

  • Set clear limits: Define acceptable thresholds for key metrics
  • Enforce in CI/CD: Block deployments that breach budgets
  • Create visibility: Make performance metrics visible to all team members

Performance Reviews

Regularly review performance as part of the development process:

  • Pre-release reviews: Check performance before major releases
  • Cross-functional reviews: Include designers, product managers, and developers
  • Learning reviews: Analyze what worked and what didn't after releases

Performance Champions

Designate team members to advocate for performance:

  • Expertise development: Invest in training and tools for champions
  • Best practice sharing: Regular knowledge sharing sessions
  • Recognition: Acknowledge and reward performance improvements

Real-World Performance Case Studies

Learning from real examples can provide valuable insights and inspiration. Here are a few case studies showcasing effective performance optimizations:

E-commerce Site Optimization

Challenge: An e-commerce site was experiencing high bounce rates on product pages, particularly on mobile devices. Analysis showed that the page load time was averaging 8 seconds on 3G connections.

Key Optimizations:

  • Image optimization: Implemented responsive images and WebP format with JPEG fallbacks, reducing image weight by 60%
  • Route-based code splitting: Only loaded JavaScript needed for the current page
  • Critical CSS inlining: Delivered above-the-fold CSS inline and deferred the rest
  • Lazy loading: Implemented for product images below the fold
  • CDN implementation: Distributed static assets globally to reduce latency

Results:

  • Page load time reduced from 8 seconds to 2.5 seconds on 3G connections
  • Mobile bounce rate decreased by 28%
  • Conversion rate improved by 15%

Key Implementation: Responsive Images with WebP

<!-- Before optimization -->
<img src="product-large.jpg" alt="Product">

<!-- After optimization -->
<picture>
<source type="image/webp" 
        srcset="product-small.webp 400w, 
                product-medium.webp 800w, 
                product-large.webp 1200w"
        sizes="(max-width: 600px) 100vw, 
               (max-width: 1200px) 50vw, 
               33vw">
<source type="image/jpeg" 
        srcset="product-small.jpg 400w, 
                product-medium.jpg 800w, 
                product-large.jpg 1200w"
        sizes="(max-width: 600px) 100vw, 
               (max-width: 1200px) 50vw, 
               33vw">
<img src="product-medium.jpg" alt="Product" 
     loading="lazy" 
     width="800" height="600">
</picture>

News Website Performance Improvement

Challenge: A news website was struggling with poor Core Web Vitals scores, particularly Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS). This was affecting both user experience and search rankings.

Key Optimizations:

  • Font optimization: Preloaded custom fonts and used font-display: swap
  • Layout stability: Set explicit dimensions for all ads, embeds, and images
  • Resource prioritization: Used resource hints (preconnect, preload) for critical resources
  • JavaScript optimization: Deferred non-critical scripts and reduced third-party impact
  • Server optimization: Improved TTFB with server-side caching and database query optimization

Results:

  • LCP improved from 4.2s to 1.9s
  • CLS reduced from 0.25 to 0.05
  • Pages passing Core Web Vitals increased from 22% to 87%
  • User engagement (time on site) increased by 17%

Key Implementation: Preventing Layout Shifts

<!-- Before - ad causing layout shift -->
<div class="ad-container">
<!-- Ad script inserts content here -->
</div>

<!-- After - reserved space prevents layout shift -->
<div class="ad-container" style="min-height: 250px; min-width: 300px;">
<!-- Ad script inserts content here -->
</div>

/* CSS for images to prevent layout shifts */
.article-image {
aspect-ratio: 16 / 9;
width: 100%;
background-color: #f0f0f0; /* Placeholder color */
}

/* CSS for web fonts to prevent layout shifts */
html {
font-display: optional; /* Or use swap if necessary */
}

/* Ensure consistent spacing even before custom fonts load */
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 
             Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}

Single Page Application (SPA) Optimization

Challenge: A React-based dashboard application was taking over 6 seconds to become interactive, leading to poor user experience, especially for users with slower devices.

Key Optimizations:

  • Bundle analysis and reduction: Identified and removed unused dependencies
  • Component-level code splitting: Loaded components on demand
  • Server-side rendering (SSR): Implemented initial server rendering for faster First Contentful Paint
  • Runtime performance: Optimized React rendering with memoization and virtualizing long lists
  • Progressive enhancement: Showed usable content before JavaScript loaded completely

Results:

  • Time to Interactive reduced from 6.2s to 2.3s
  • JavaScript bundle size reduced by 45%
  • CPU usage during interaction reduced by 30%
  • User-reported satisfaction increased by 40%

Key Implementation: Component-Level Code Splitting

// Before optimization - importing everything upfront
import Dashboard from './components/Dashboard';
import Reports from './components/Reports';
import Analytics from './components/Analytics';
import Settings from './components/Settings';

// After optimization - dynamic imports
import React, { lazy, Suspense } from 'react';

// Only the initial route component is loaded eagerly
import Dashboard from './components/Dashboard';

// Other components are loaded on demand
const Reports = lazy(() => import('./components/Reports'));
const Analytics = lazy(() => import('./components/Analytics'));
const Settings = lazy(() => import('./components/Settings'));

function App() {
return (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route path="/" exact component={Dashboard} />
        <Route path="/reports" component={Reports} />
        <Route path="/analytics" component={Analytics} />
        <Route path="/settings" component={Settings} />
      </Switch>
    </Suspense>
  </Router>
);
}

Web Performance Checklist

Use this comprehensive checklist to evaluate and improve the performance of your web applications. This can serve as a guide during development or as an audit tool for existing projects.

Initial Loading Performance

Resource Optimization

  • Minify and compress HTML, CSS, and JavaScript
  • Optimize images using modern formats and proper sizing
  • Implement responsive images with srcset and sizes
  • Use lazy loading for below-the-fold images and iframes
  • Optimize fonts with proper format selection and font-display
  • Remove unused CSS and JavaScript from the bundle

Critical Rendering Path

  • Minimize render-blocking resources by deferring or asynchronously loading non-critical scripts
  • Inline critical CSS for above-the-fold content
  • Use server-side rendering or pre-rendering where appropriate
  • Implement resource hints (preload, preconnect, prefetch) for critical resources
  • Ensure efficient order of loading for critical resources

Delivery Optimization

  • Enable compression (Gzip or Brotli) for text-based resources
  • Implement proper caching with appropriate headers
  • Use a CDN for static assets
  • Enable HTTP/2 or HTTP/3 for multiplexing and reduced latency
  • Optimize server response time (TTFB)

JavaScript Delivery

  • Split code into smaller bundles
  • Implement tree shaking to remove unused code
  • Use modern script loading attributes (async/defer) appropriately
  • Prioritize critical JavaScript execution
  • Consider module/nomodule pattern for differential serving

Runtime Performance

JavaScript Efficiency

  • Optimize long-running JavaScript tasks
  • Use Web Workers for CPU-intensive operations
  • Implement virtualization for long lists
  • Throttle and debounce event listeners appropriately
  • Use passive event listeners for scroll and touch events
  • Avoid memory leaks by cleaning up event listeners and references

Rendering Performance

  • Minimize layout thrashing by batching DOM reads and writes
  • Use CSS transforms and opacity for animations
  • Avoid forced synchronous layouts
  • Use requestAnimationFrame for visual updates
  • Reduce paint complexity and paint areas
  • Use will-change appropriately (but sparingly)

Layout Stability

  • Set explicit dimensions for images and media
  • Reserve space for dynamic content like ads and embeds
  • Avoid inserting content above existing content
  • Use transform animations instead of animating properties that trigger layout

Performance Monitoring

Metrics and Measurement

  • Track Core Web Vitals (LCP, FID, CLS)
  • Implement Real User Monitoring (RUM) for field data
  • Set up synthetic testing for lab data
  • Create performance dashboards for visibility
  • Establish performance budgets and enforce them

Continuous Improvement

  • Include performance testing in CI/CD pipeline
  • Set up performance regression alerts
  • Review performance metrics regularly
  • Conduct performance audits before major releases
  • Foster a performance culture within the development team

Mobile-First Performance Strategy

With mobile traffic dominating for many websites, a mobile-first approach to performance is essential. This means optimizing for mobile constraints first, then enhancing the experience for more capable devices and connections.

Understanding Mobile Constraints

Mobile devices face unique challenges:

  • Network variability: Mobile connections can range from fast 5G to spotty 2G or worse
  • CPU limitations: Mobile processors are typically less powerful than desktop CPUs
  • Memory constraints: Less RAM means more aggressive background tab handling
  • Battery concerns: Performance-intensive websites drain batteries faster
  • Input methods: Touch interfaces have different interaction patterns than mouse/keyboard

Progressive Enhancement

Start with a minimal but functional experience, then enhance it for more capable devices:

/* Base styles for all devices */
.card {
padding: 1rem;
margin-bottom: 1rem;
background-color: #fff;
border: 1px solid #ddd;
}

/* Enhanced styles for devices with hover capability */
@media (hover: hover) {
.card {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
}

/* Enhanced styles for larger screens with more capabilities */
@media (min-width: 768px) and (min-resolution: 2dppx) {
.card {
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
}

Adaptive Loading

Adjust the experience based on the user's device capabilities and network conditions:

// Detect network conditions and capabilities
function getExperienceLevel() {
// Start with the assumption of a constrained experience
let level = 'lite';

// Check for device memory
if (navigator.deviceMemory > 4) {
  level = 'medium';
}

// Check for network conditions
if (navigator.connection) {
  const connection = navigator.connection;
  
  // Low data mode
  if (connection.saveData) {
    return 'lite'; // Override with lite experience
  }
  
  // Fast connection
  if (connection.effectiveType === '4g' && !connection.saveData) {
    level = level === 'medium' ? 'full' : 'medium';
  }
}

return level;
}

// Adjust the experience based on capabilities
function loadAppropriateExperience() {
const experienceLevel = getExperienceLevel();

switch (experienceLevel) {
  case 'lite':
    // Load minimal assets
    loadLiteAssets();
    break;
  case 'medium':
    // Load standard assets
    loadMediumAssets();
    break;
  case 'full':
    // Load enhanced assets
    loadFullAssets();
    break;
}
}

// Example implementations
function loadLiteAssets() {
// Load low-resolution images
document.querySelectorAll('[data-src-lite]').forEach(img => {
  img.src = img.getAttribute('data-src-lite');
});

// Load essential scripts only
import('./essential.js');
}

function loadMediumAssets() {
// Load standard resolution images
document.querySelectorAll('[data-src-medium]').forEach(img => {
  img.src = img.getAttribute('data-src-medium');
});

// Load standard features
import('./essential.js').then(() => import('./standard.js'));
}

function loadFullAssets() {
// Load high-resolution images
document.querySelectorAll('[data-src-full]').forEach(img => {
  img.src = img.getAttribute('data-src-full');
});

// Load all features
import('./essential.js')
  .then(() => import('./standard.js'))
  .then(() => import('./enhanced.js'));
}

Offline Support with Service Workers

Service workers enable offline functionality and can significantly improve perceived performance:

// Register a service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
  navigator.serviceWorker.register('/sw.js')
    .then(registration => {
      console.log('ServiceWorker registered:', registration.scope);
    })
    .catch(error => {
      console.error('ServiceWorker registration failed:', error);
    });
});
}

// In sw.js (Service Worker file)
const CACHE_NAME = 'v1-cache';
const URLS_TO_CACHE = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/main.js',
'/offline.html',
'/images/logo.svg'
];

// Install event - cache critical assets
self.addEventListener('install', event => {
event.waitUntil(
  caches.open(CACHE_NAME)
    .then(cache => {
      return cache.addAll(URLS_TO_CACHE);
    })
);
});

// Fetch event - serve from cache or network
self.addEventListener('fetch', event => {
event.respondWith(
  caches.match(event.request)
    .then(response => {
      // Cache hit - return the response from cache
      if (response) {
        return response;
      }
      
      // No cache match - fetch from network
      return fetch(event.request)
        .then(response => {
          // Check if we received a valid response
          if (!response || response.status !== 200 || response.type !== 'basic') {
            return response;
          }
          
          // Clone the response as it can only be used once
          const responseToCache = response.clone();
          
          // Open the cache and store the response
          caches.open(CACHE_NAME)
            .then(cache => {
              cache.put(event.request, responseToCache);
            });
          
          return response;
        })
        .catch(() => {
          // Network failed - serve offline page for HTML requests
          if (event.request.mode === 'navigate') {
            return caches.match('/offline.html');
          }
        });
    })
);
});

Touch Optimization

Optimize for touch interactions to improve perceived performance:

  • Make touch targets at least 44×44 pixels for easy interaction
  • Implement 'instant' visual feedback for touch actions
  • Minimize touch delay by avoiding unnecessary event listeners
  • Ensure smooth scrolling with optimized event handlers
/* CSS for touch-friendly targets */
.button {
min-width: 44px;
min-height: 44px;
padding: 12px 16px;
/* Visual padding can be smaller than the actual touch target */
}

/* CSS for instant touch feedback */
.button {
transition: background-color 0.1s;
}

.button:active {
background-color: #e0e0e0;
/* Fast transition for active state */
}

/* JavaScript for smoother scrolling */
// Use passive listeners for scroll events
document.addEventListener('scroll', function() {
// Scroll handling code
}, { passive: true });

Practical Exercise: Performance Audit and Optimization

Let's apply what we've learned with a hands-on exercise. In this exercise, you'll audit a web page for performance issues and implement optimizations to improve its speed and user experience.

Step 1: Initial Performance Analysis

  1. Choose a web page to optimize (your own project or a sample page)
  2. Run a Lighthouse audit in Chrome DevTools (Lighthouse tab)
  3. Note the key performance metrics and opportunities for improvement
  4. Use the Network panel to identify slow-loading resources
  5. Use the Performance panel to identify JavaScript and rendering bottlenecks

Step 2: Create a Performance Improvement Plan

Based on your analysis, create a prioritized list of optimizations to implement. Focus on:

  1. Quick wins with high impact
  2. Critical rendering path optimizations
  3. Resource optimizations (images, scripts, styles)
  4. Runtime performance improvements

Step 3: Implement Optimizations

Implement the planned optimizations. Here are some examples:

Image Optimization

<!-- Before -->
<img src="large-image.jpg" alt="Description">

<!-- After -->
<img src="large-image.jpg" 
   srcset="small-image.jpg 400w, medium-image.jpg 800w, large-image.jpg 1200w" 
   sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw" 
   alt="Description" 
   loading="lazy" 
   width="800" 
   height="600">

Critical CSS Extraction

<!-- Before -->
<link rel="stylesheet" href="styles.css">

<!-- After -->
<style>
/* Critical CSS inlined */
body { font-family: sans-serif; margin: 0; }
.header { background: #f5f5f5; padding: 1rem; }
.hero { height: 50vh; background: linear-gradient(#1a2a6c, #b21f1f); color: white; display: flex; align-items: center; justify-content: center; }
/* End critical CSS */
</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>

JavaScript Optimization

<!-- Before -->
<script src="app.js"></script>

<!-- After -->
<script src="critical.js"></script>
<script src="app.js" defer></script>

// In app.js - use dynamic imports for non-critical features
document.getElementById('feature-button').addEventListener('click', async () => {
const { initFeature } = await import('./feature.js');
initFeature();
});

Step 4: Measure Improvement

  1. Run another Lighthouse audit after implementing your optimizations
  2. Compare before and after metrics
  3. Identify remaining opportunities for improvement
  4. Document what worked best and what didn't make a significant difference

Step 5: Advanced Optimizations

If time permits, implement more advanced optimizations:

  • Set up a simple service worker for caching
  • Implement resource hints (preconnect, preload) for critical resources
  • Add performance monitoring with the Performance API
  • Optimize third-party script loading

Simple Performance Monitoring

// Add to your main JavaScript file
window.addEventListener('load', () => {
// Wait for all resources to finish loading
setTimeout(() => {
  // Get performance metrics
  const perfEntries = performance.getEntriesByType('navigation')[0];
  const paintEntries = performance.getEntriesByType('paint');
  
  // Calculate key metrics
  const metrics = {
    // Time to First Byte (TTFB)
    ttfb: perfEntries.responseStart - perfEntries.requestStart,
    
    // DOM Content Loaded
    domContentLoaded: perfEntries.domContentLoadedEventEnd - perfEntries.startTime,
    
    // Load Time
    loadTime: perfEntries.loadEventEnd - perfEntries.startTime,
    
    // First Paint
    firstPaint: findMetric(paintEntries, 'first-paint'),
    
    // First Contentful Paint
    firstContentfulPaint: findMetric(paintEntries, 'first-contentful-paint')
  };
  
  // Log metrics
  console.table(metrics);
  
  // Send to analytics if needed
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/analytics', JSON.stringify(metrics));
  }
}, 0);
});

function findMetric(entries, name) {
const entry = entries.find(entry => entry.name === name);
return entry ? entry.startTime : 0;
}

Conclusion

Web performance is a cornerstone of exceptional user experience. By understanding the principles, measuring the right metrics, and implementing appropriate optimizations, you can create web applications that load quickly, respond immediately, and provide a smooth experience across all devices and networks.

Key Takeaways

  • Performance matters for user experience, business metrics, and SEO
  • Measure before optimizing to identify the most impactful improvements
  • Focus on the critical rendering path to improve perceived performance
  • Optimize resource loading with proper sizing, formats, and delivery strategies
  • Consider all users, especially those on mobile and slow connections
  • Establish a performance culture with ongoing monitoring and maintenance

Continuing Your Performance Journey

Web performance is a deep field with much to explore. Here are resources to continue your learning:

Remember, performance optimization is a continuous process, not a one-time task. As web technologies evolve and user expectations increase, the pursuit of better performance will remain a worthy investment of your time and effort. The skills you've learned in this session form a foundation that will serve you well throughout your career as a web developer.