Long Tasks | Blue Frog Docs

Long Tasks

Identifying and fixing long-running JavaScript tasks that block the main thread

Long Tasks

Long tasks are JavaScript operations that run for more than 50 milliseconds, blocking the browser's main thread and causing responsiveness issues.

What This Means

When JavaScript runs for too long without yielding, users experience:

How to Diagnose

Chrome DevTools Performance

  1. Open DevTools > Performance
  2. Record a page interaction
  3. Look for red bars (long tasks)
  4. Identify scripts causing issues

Using Long Tasks API

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

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

Web Vitals Attribution

import { onINP } from 'web-vitals/attribution';

onINP((metric) => {
  console.log('INP:', metric.value);
  console.log('Attribution:', metric.attribution);
});

General Fixes

1. Break Up Long Tasks

Use requestIdleCallback or setTimeout:

// Before: Long synchronous task
function processLargeArray(items) {
  items.forEach(item => processItem(item));
}

// After: Chunked processing
function processLargeArrayChunked(items, chunkSize = 100) {
  let index = 0;

  function processChunk() {
    const chunk = items.slice(index, index + chunkSize);
    chunk.forEach(item => processItem(item));
    index += chunkSize;

    if (index < items.length) {
      requestIdleCallback(processChunk);
    }
  }

  requestIdleCallback(processChunk);
}

2. Use Web Workers

Offload heavy computation:

// main.js
const worker = new Worker('heavy-computation.js');

worker.postMessage({ data: largeDataSet });

worker.onmessage = (e) => {
  const result = e.data;
  updateUI(result);
};

// heavy-computation.js
self.onmessage = (e) => {
  const result = expensiveCalculation(e.data);
  self.postMessage(result);
};

3. Defer Non-Critical JavaScript

Use async/defer attributes:

<!-- Critical scripts -->
<script src="critical.js"></script>

<!-- Non-critical deferred -->
<script defer src="analytics.js"></script>
<script defer src="chat-widget.js"></script>

4. Optimize Event Handlers

Debounce expensive handlers:

// Before: Runs on every scroll
window.addEventListener('scroll', handleScroll);

// After: Debounced
function debounce(fn, delay) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn.apply(this, args), delay);
  };
}

window.addEventListener('scroll', debounce(handleScroll, 100));

5. Use isInputPending

Check for pending input before continuing:

async function processItems(items) {
  for (const item of items) {
    processItem(item);

    // Yield if user is waiting
    if (navigator.scheduling?.isInputPending()) {
      await new Promise(resolve => setTimeout(resolve, 0));
    }
  }
}

6. Code Split Large Bundles

Reduce initial JavaScript:

// Lazy load non-critical modules
const heavyModule = await import('./heavy-module.js');

// Route-based code splitting
const ProductPage = React.lazy(() => import('./ProductPage'));

Thresholds

Duration Classification Impact
< 50ms Normal Good responsiveness
50-100ms Long task Minor jank
100-250ms Very long Noticeable delay
> 250ms Critical Poor user experience

Monitoring

Track long tasks in production:

// Send to analytics
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    if (entry.duration > 100) {
      gtag('event', 'long_task', {
        duration: Math.round(entry.duration),
        page: window.location.pathname
      });
    }
  });
});

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

Further Reading

// SYS.FOOTER