Main Thread Blocking Issues | Blue Frog Docs

Main Thread Blocking Issues

Understanding and fixing long tasks blocking the main thread and degrading performance

Main Thread Blocking Issues

What This Means

The browser's main thread handles user interactions, JavaScript execution, rendering, and layout. When long tasks (>50ms) block the main thread, the browser cannot respond to user input, causing:

  • Janky scrolling and animations
  • Delayed response to clicks and taps
  • Poor Interaction to Next Paint (INP) scores
  • Sluggish interface feeling unresponsive
  • Dropped frames and visual stuttering

Main thread blocking is one of the most common performance issues affecting user experience, especially on lower-powered devices.

How to Diagnose

Chrome DevTools Performance Panel

  1. Record performance profile during user interaction
  2. Look for red triangles indicating long tasks (>50ms)
  3. Check the Main thread flame chart for long blocks
  4. Use Performance Insights for automated detection

Performance Observer API

// Detect long tasks
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    console.log('Long task detected:', {
      duration: entry.duration,
      startTime: entry.startTime,
      attribution: entry.attribution
    });
  });
});

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

Lighthouse

  • Check "Minimize main-thread work" audit
  • Review "Avoid long main-thread tasks" warning
  • Look at Total Blocking Time (TBT) metric

Common Causes

  • Heavy JavaScript parsing and compilation
  • Complex DOM manipulation
  • Synchronous layout calculations (layout thrashing)
  • Large JSON parsing
  • Heavy image or video processing

General Fixes

  1. Break up long tasks with yielding

    // Bad: Blocks for entire loop
    for (let i = 0; i < 1000; i++) {
      processItem(i);
    }
    
    // Good: Yield to main thread
    async function processItems() {
      for (let i = 0; i < 1000; i++) {
        processItem(i);
        if (i % 50 === 0) {
          await new Promise(resolve => setTimeout(resolve, 0));
        }
      }
    }
    
  2. Use requestIdleCallback for non-critical work

    requestIdleCallback((deadline) => {
      while (deadline.timeRemaining() > 0 && tasks.length > 0) {
        processTask(tasks.shift());
      }
    });
    
  3. Offload to Web Workers - Move heavy computation off the main thread

    const worker = new Worker('worker.js');
    worker.postMessage(data);
    worker.onmessage = (e) => updateUI(e.data);
    
  4. Debounce expensive operations

    const debouncedSearch = debounce((query) => {
      performSearch(query);
    }, 300);
    
  5. Use virtualization for long lists - Only render visible items

    // Libraries: react-window, react-virtualized
    <VirtualList items={items} height={600} itemHeight={50} />
    
  6. Optimize React rendering - Use React.memo, useMemo, useCallback

    const MemoizedComponent = React.memo(Component);
    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    
  7. Batch DOM updates - Group reads and writes

    // Use requestAnimationFrame for visual updates
    requestAnimationFrame(() => {
      element.style.height = `${newHeight}px`;
    });
    
  8. Code splitting - Load JavaScript on demand

    const LazyComponent = lazy(() => import('./Component'));
    
  9. Optimize third-party scripts - Load non-critical scripts asynchronously

    <script src="analytics.js" async></script>
    <script src="chat.js" defer></script>
    
  10. Use CSS animations over JavaScript - Let the compositor thread handle animations

Platform-Specific Guides

Platform Guide
React Optimize Rendering Performance
Next.js Script Optimization
Angular Zone.js Optimization
Vue Render Function Optimization

Further Reading

// SYS.FOOTER