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:
- Delayed click responses
- Janky scrolling
- Input lag
- Poor Interaction to Next Paint (INP) scores
How to Diagnose
Chrome DevTools Performance
- Open DevTools > Performance
- Record a page interaction
- Look for red bars (long tasks)
- 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'] });