← Back to Articles

Frontend Performance Optimization Techniques

Code
5 min read

Frontend performance is crucial for user experience and business metrics. Slow applications lose users and hurt conversion rates. I've optimized numerous applications, and the techniques I've learned can make a dramatic difference.

Performance metrics that matter

Focus on Core Web Vitals:

  • Largest Contentful Paint (LCP) - Loading performance
  • First Input Delay (FID) - Interactivity
  • Cumulative Layout Shift (CLS) - Visual stability

Also track:

  • Time to First Byte (TTFB)
  • First Contentful Paint (FCP)
  • Total Blocking Time (TBT)

Bundle size optimization

Large bundles slow down initial page loads. Reduce bundle size through:

  1. Code splitting - Split code into smaller chunks
  2. Tree shaking - Remove unused code
  3. Dynamic imports - Load code on demand
// Dynamic import for route-based code splitting
const HomePage = lazy(() =>
  import('./pages/HomePage')
);

// Component-based code splitting
const HeavyComponent = lazy(() =>
  import('./components/HeavyComponent')
);

Image optimization

Images often account for most of a page's weight. Optimize with:

  • Modern formats: WebP, AVIF
  • Responsive images: Different sizes for different screens
  • Lazy loading: Load images as they enter viewport
<!-- Responsive images -->
<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="Description"
       loading="lazy"
       width="800" height="600">
</picture>

Critical rendering path optimization

Optimize the path from HTML to pixels:

  1. Minimize render-blocking resources
  2. Optimize CSS delivery
  3. Prioritize above-the-fold content
<!-- Inline critical CSS -->
<style>
  .hero { background: #f0f0f0; }
  .hero h1 { font-size: 2rem; }
</style>

<!-- Defer non-critical CSS -->
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

JavaScript performance

JavaScript can block rendering and interaction:

  • Minify and compress JavaScript
  • Remove unused code with tree shaking
  • Use efficient algorithms
  • Avoid memory leaks
// Memory leak example (bad)
class Component {
  constructor() {
    this.listeners = [];
    document.addEventListener('click', this.handleClick); // Never removed
  }
}

// Fixed version
class Component {
  constructor() {
    this.handleClick = this.handleClick.bind(this);
    document.addEventListener('click', this.handleClick);
  }

  destroy() {
    document.removeEventListener('click', this.handleClick);
  }
}

Virtual scrolling for large lists

For lists with hundreds or thousands of items:

class VirtualList {
  constructor(container, items, itemHeight) {
    this.container = container;
    this.items = items;
    this.itemHeight = itemHeight;
    this.visibleItems = 10;
    this.scrollTop = 0;

    this.setupScrollListener();
    this.render();
  }

  setupScrollListener() {
    this.container.addEventListener('scroll', () => {
      this.scrollTop = this.container.scrollTop;
      this.render();
    });
  }

  render() {
    const startIndex = Math.floor(this.scrollTop / this.itemHeight);
    const endIndex = startIndex + this.visibleItems;

    const visibleItems = this.items.slice(startIndex, endIndex);
    const offsetY = startIndex * this.itemHeight;

    this.container.innerHTML = visibleItems
      .map(item => `<div style="height: ${this.itemHeight}px">${item}</div>`)
      .join('');

    this.container.style.transform = `translateY(${offsetY}px)`;
  }
}

Caching strategies

Leverage browser caching:

  • HTTP caching headers
  • Service Worker caching
  • CDN caching
// Service Worker for caching
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) => {
      return cache.addAll([
        '/',
        '/styles.css',
        '/app.js'
      ]);
    })
  );
});

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

Font loading optimization

Fonts can cause layout shifts and slow loading:

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

<!-- Font display swap to prevent invisible text -->
@font-face {
  font-family: 'Inter';
  src: url('inter.woff2') format('woff2');
  font-display: swap;
}

Runtime performance

Optimize for smooth interactions:

  • Debounce expensive operations
  • Use requestAnimationFrame for animations
  • Avoid forced synchronous layouts
// Debounced search
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

const debouncedSearch = debounce(searchFunction, 300);

// Use RAF for smooth animations
function animate() {
  // Update animation
  requestAnimationFrame(animate);
}

Monitoring and measurement

Track performance with:

  • Lighthouse for audits
  • Web Vitals for real user monitoring
  • Performance API for programmatic measurement
// Measure performance
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('LCP:', entry.startTime);
  }
});

observer.observe({ entryTypes: ['largest-contentful-paint'] });

// Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);

Build optimization

Optimize your build process:

  • Webpack bundle analyzer to find large dependencies
  • CSS extraction and optimization
  • Asset optimization (images, fonts, etc.)

Progressive enhancement

Build for the best experience but ensure graceful degradation:

  • Core functionality works without JavaScript
  • Progressive loading of enhancements
  • Fallbacks for failed resources

Performance budgets

Set performance budgets and enforce them:

// webpack performance hints
module.exports = {
  performance: {
    hints: 'warning',
    maxAssetSize: 244000,
    maxEntrypointSize: 244000,
  }
};

Mobile optimization

Mobile devices have limited resources:

  • Touch targets at least 44px
  • Optimized images for mobile screens
  • Reduced motion for accessibility
  • Battery-conscious JavaScript

Continuous monitoring

Performance optimization is ongoing:

  • Regular audits with Lighthouse
  • A/B testing for performance changes
  • User feedback collection
  • Synthetic monitoring for key journeys

Frontend performance optimization is a continuous process. Start with the basics, measure your improvements, and iterate. Small changes can have big impacts on user experience and business metrics.

About the author

Rafael De Paz

Full Stack Developer

Passionate full-stack developer specializing in building high-quality web applications and responsive sites. Expert in robust data handling, leveraging modern frameworks, cloud technologies, and AI tools to deliver scalable, high-performance solutions that drive user engagement and business growth. I harness AI technologies to accelerate development, testing, and debugging workflows.

Tags:

Share: