P2Issue #0
Avoid long main-thread tasks
❓ What does it mean?
❓ What does it mean?
The main thread is where the browser executes JavaScript, handles user interactions, and performs rendering tasks.
A long main-thread task is any JavaScript execution that takes longer than 50 milliseconds to complete.
When long tasks block the main thread:
• The browser cannot respond to user input (clicks, scrolls, typing)
• The page appears frozen or unresponsive
• Animations may stutter or lag
Examples of long tasks:
• Heavy JavaScript parsing and execution
• Complex DOM manipulations
• Large data processing operations
• Synchronous API calls
🚨 Why is it important for SEO?
🚨 Why is this a problem for SEO?
Poor User Experience – Users experience lag when clicking buttons or scrolling.
Core Web Vitals Impact – Long tasks directly hurt:
• Total Blocking Time (TBT)
• Interaction to Next Paint (INP)
• First Input Delay (FID)
SEO Rankings – Google uses INP as a ranking signal; long tasks hurt this metric.
Higher Bounce Rate – Unresponsive pages frustrate users, causing them to leave.
Mobile Performance – Mobile devices with slower CPUs are especially affected.
✅ Target: Keep tasks under 50ms
⚠️ Acceptable: 50-200ms
❌ Poor: > 200ms
✅ How to Fix It
✅ Best Practices to Fix
Break up long tasks into smaller chunks:
• Use setTimeout() or requestIdleCallback() to yield to the browser
• Split work into multiple frames
Code splitting and lazy loading:
• Load only necessary JavaScript for each page
• Use dynamic imports in React/Next.js
Defer non-critical work:
• Move heavy computations to Web Workers
• Delay analytics and tracking scripts
Optimize JavaScript execution:
• Avoid synchronous operations
• Minimize DOM manipulation
• Use virtual scrolling for long lists
Use React/Next.js optimization:
• React.memo() to prevent unnecessary re-renders
• useMemo() and useCallback() for expensive computations
• Concurrent rendering features
Profile and identify long tasks:
• Chrome DevTools → Performance tab
• Lighthouse → Diagnostics section
❌ Bad Example
📌 Example
❌ Bad (Long Task Blocking Main Thread):
// Heavy synchronous operation - blocks for 800ms
function processLargeData() {
const data = fetchLargeDataset(); // 10,000 items
// Process all data at once - BLOCKS main thread
const results = [];
for (let i = 0; i < data.length; i++) {
// Complex calculation for each item
results.push(expensiveCalculation(data[i]));
}
return results;
}
button.addEventListener('click', () => {
const results = processLargeData(); // 800ms blocking task!
displayResults(results);
});
👉 When user clicks button:
• Main thread is blocked for 800ms
• User cannot interact with page
• Page appears frozen
• INP score: Poor (> 500ms)
✅ Good Example
✅ Good (Breaking Long Task into Chunks):
// Break work into smaller chunks
async function processLargeDataInChunks() {
const data = fetchLargeDataset(); // 10,000 items
const results = [];
const chunkSize = 100;
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
// Process chunk
for (const item of chunk) {
results.push(expensiveCalculation(item));
}
// Yield to browser after each chunk (allow user interactions)
await new Promise(resolve => setTimeout(resolve, 0));
}
return results;
}
button.addEventListener('click', async () => {
showLoadingSpinner();
const results = await processLargeDataInChunks(); // Multiple small tasks
hideLoadingSpinner();
displayResults(results);
});
👉 Work is split into 100 chunks of 100 items each
👉 Each chunk takes ~8ms (under 50ms threshold)
👉 Browser can respond to user input between chunks
👉 INP score: Good (< 200ms)
✅ Even Better (Using Web Workers):
// main.js
const worker = new Worker('data-processor.worker.js');
button.addEventListener('click', () => {
showLoadingSpinner();
// Offload heavy work to Web Worker (runs in separate thread)
worker.postMessage({ data: fetchLargeDataset() });
worker.onmessage = (e) => {
hideLoadingSpinner();
displayResults(e.data);
};
});
// data-processor.worker.js
self.onmessage = (e) => {
const results = [];
for (const item of e.data.data) {
results.push(expensiveCalculation(item));
}
self.postMessage(results);
};
👉 Heavy computation runs in background thread
👉 Main thread remains responsive
👉 User can continue interacting with page
👉 Perfect INP score
✅ React/Next.js Example:
import { useState, useTransition } from 'react';
function DataProcessor() {
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleProcess = () => {
startTransition(() => {
// React 18 concurrent rendering - breaks into chunks automatically
const data = fetchLargeDataset();
const processed = data.map(expensiveCalculation);
setResults(processed);
});
};
return (
<div>
<button onClick={handleProcess} disabled={isPending}>
{isPending ? 'Processing...' : 'Process Data'}
</button>
{results.map(r => <div key={r.id}>{r.value}</div>)}
</div>
);
}
⚡ Result
⚡ SEO & UX Impact of Fixing
Improved Core Web Vitals:
• Better INP (Interaction to Next Paint) - Google ranking signal
• Lower TBT (Total Blocking Time)
• Reduced FID (First Input Delay)
Higher SEO Rankings – Google rewards responsive, interactive pages.
Better User Experience – Pages feel smooth and responsive.
Increased Engagement – Users are more likely to complete actions.
Mobile Performance – Critical for slower mobile devices.
👉 Long Task Optimization Checklist:
✅ Break tasks into chunks < 50ms
✅ Use Web Workers for heavy computation
✅ Implement code splitting and lazy loading
✅ Defer non-critical JavaScript
✅ Use React concurrent features (useTransition)
✅ Profile with Chrome DevTools Performance tab
✅ Monitor INP in Google Search Console
👉 Performance Targets:
• Individual tasks: < 50ms
• INP (Interaction to Next Paint): < 200ms
• TBT (Total Blocking Time): < 200ms