Web Performance Optimization Guide

11 min read

Web Performance Optimization Guide

Web performance is crucial for user experience and SEO. Here's a comprehensive guide to optimizing your web applications.

Core Web Vitals

Largest Contentful Paint (LCP)

Optimize your largest content element:

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

<!-- Optimize images -->
<img src="/hero-image.jpg" 
     alt="Hero" 
     loading="eager"
     fetchpriority="high"
     width="800" 
     height="600">

First Input Delay (FID)

Reduce JavaScript execution time:

// Use requestIdleCallback for non-critical tasks
function processNonCriticalTasks() {
  if ('requestIdleCallback' in window) {
    requestIdleCallback(() => {
      // Non-critical work here
      processAnalytics()
      loadNonEssentialFeatures()
    })
  } else {
    setTimeout(processNonCriticalTasks, 1)
  }
}

// Code splitting for better performance
const LazyComponent = React.lazy(() => import('./LazyComponent'))

Cumulative Layout Shift (CLS)

Prevent layout shifts:

/* Reserve space for images */
.image-container {
  aspect-ratio: 16 / 9;
  background-color: #f0f0f0;
}

/* Use transform instead of changing layout properties */
.animated-element {
  transform: translateX(0);
  transition: transform 0.3s ease;
}

.animated-element:hover {
  transform: translateX(10px);
}

Resource Optimization

Image Optimization

<!-- Modern image formats with fallbacks -->
<picture>
  <source srcset="/image.avif" type="image/avif">
  <source srcset="/image.webp" type="image/webp">
  <img src="/image.jpg" alt="Description" loading="lazy">
</picture>

<!-- Responsive images -->
<img srcset="/image-320w.jpg 320w,
             /image-640w.jpg 640w,
             /image-1280w.jpg 1280w"
     sizes="(max-width: 320px) 280px,
            (max-width: 640px) 600px,
            1200px"
     src="/image-640w.jpg"
     alt="Responsive image">

Font Optimization

/* Font display strategy */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom-font.woff2') format('woff2');
  font-display: swap; /* Show fallback font immediately */
}

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

JavaScript Optimization

Bundle Splitting

// Webpack configuration
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
}

// Dynamic imports
async function loadFeature() {
  const { feature } = await import('./feature.js')
  return feature()
}

Tree Shaking

// Use named imports for better tree shaking
import { debounce } from 'lodash-es'

// Instead of
import _ from 'lodash'

// Configure webpack for better tree shaking
module.exports = {
  mode: 'production',
  optimization: {
    usedExports: true,
    sideEffects: false,
  },
}

Caching Strategies

HTTP Caching

// Service Worker caching
self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(
      caches.open('images').then((cache) => {
        return cache.match(event.request).then((response) => {
          if (response) {
            return response
          }
          return fetch(event.request).then((fetchResponse) => {
            cache.put(event.request, fetchResponse.clone())
            return fetchResponse
          })
        })
      })
    )
  }
})

Browser Caching Headers

// Express.js example
app.use('/static', express.static('public', {
  maxAge: '1y', // Cache static assets for 1 year
  etag: true,
  lastModified: true,
}))

// Set cache headers for API responses
app.get('/api/data', (req, res) => {
  res.set('Cache-Control', 'public, max-age=300') // 5 minutes
  res.json(data)
})

Critical Resource Prioritization

Resource Hints

<!-- DNS prefetch for external domains -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//api.example.com">

<!-- Preconnect for critical third-party resources -->
<link rel="preconnect" href="//fonts.gstatic.com" crossorigin>

<!-- Prefetch for likely next navigation -->
<link rel="prefetch" href="/next-page.html">

<!-- Preload critical resources -->
<link rel="preload" href="/critical.css" as="style">
<link rel="preload" href="/hero.jpg" as="image">

Critical CSS

<style>
  /* Inline critical CSS */
  .header { display: flex; justify-content: space-between; }
  .hero { background: url('/hero.jpg'); height: 60vh; }
</style>

<!-- Load non-critical CSS asynchronously -->
<link rel="preload" href="/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/non-critical.css"></noscript>

Performance Monitoring

Web Vitals Measurement

import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'

function sendToAnalytics(metric) {
  // Send to your analytics service
  gtag('event', metric.name, {
    value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
    event_category: 'Web Vitals',
    event_label: metric.id,
    non_interaction: true,
  })
}

getCLS(sendToAnalytics)
getFID(sendToAnalytics)
getFCP(sendToAnalytics)
getLCP(sendToAnalytics)
getTTFB(sendToAnalytics)

Performance Observer

// Monitor long tasks
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 50) {
      console.warn('Long task detected:', entry)
    }
  }
})

observer.observe({ entryTypes: ['longtask'] })

// Monitor layout shifts
const clsObserver = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) {
      console.log('Layout shift:', entry.value)
    }
  }
})

clsObserver.observe({ entryTypes: ['layout-shift'] })

Advanced Techniques

Intersection Observer for Lazy Loading

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src
      img.classList.remove('lazy')
      observer.unobserve(img)
    }
  })
})

document.querySelectorAll('img[data-src]').forEach(img => {
  imageObserver.observe(img)
})

Virtual Scrolling

class VirtualScroller {
  constructor(container, itemHeight, totalItems) {
    this.container = container
    this.itemHeight = itemHeight
    this.totalItems = totalItems
    this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 1
    
    this.render()
    container.addEventListener('scroll', this.onScroll.bind(this))
  }
  
  onScroll() {
    const scrollTop = this.container.scrollTop
    const startIndex = Math.floor(scrollTop / this.itemHeight)
    this.render(startIndex)
  }
  
  render(startIndex = 0) {
    const endIndex = Math.min(startIndex + this.visibleItems, this.totalItems)
    const items = []
    
    for (let i = startIndex; i < endIndex; i++) {
      items.push(this.createItem(i))
    }
    
    this.container.innerHTML = ''
    this.container.append(...items)
    this.container.scrollTop = startIndex * this.itemHeight
  }
}

Performance Budget

// webpack-bundle-analyzer configuration
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
    }),
  ],
  performance: {
    maxAssetSize: 250000,
    maxEntrypointSize: 250000,
    hints: 'warning',
  },
}

Conclusion

Web performance optimization is an ongoing process that requires monitoring, measurement, and continuous improvement. Focus on Core Web Vitals, optimize critical resources, implement effective caching strategies, and use performance monitoring tools to ensure your web applications provide excellent user experiences.