Break Up Long Tasks
Split JavaScript tasks longer than 50ms into smaller chunks so the browser stays responsive.
Metrics
This technique does not move LCP.
This technique can move up to 100% of TBT's share of the Lighthouse score.
This technique does not move CLS.
This technique does not move FCP.
This technique can move up to 33% of SI's share of the Lighthouse score.
This technique does not move TTFB.
Overview
The browser’s main thread processes JavaScript, handles user input, performs layout, and paints to screen — all on a single thread. Any JavaScript task longer than 50 ms is a long task that blocks the thread from responding to user input for its entire duration. Total Blocking Time is the sum of all time beyond 50 ms across all long tasks during page load. Breaking long tasks into smaller yielding chunks eliminates TBT and dramatically improves Interaction to Next Paint.
Impact grade, A+ to D: how much of the Lighthouse score this technique can move. Unaffected: LCP, CLS, FCP, TTFB.
The Problem
On a typical WordPress page with a page builder, analytics, chat widget, and slider, the main thread can be blocked for 2–5 seconds during load by a cascade of long tasks. Each 200 ms task is one 150 ms contribution to TBT. A user clicking a button during this window waits the full remaining task duration before getting a response — the defining cause of poor INP scores. Lighthouse surfaces this as ‘Minimize main-thread work’ and ‘Reduce JavaScript execution time’.
Performance Impact
TBT
100–600ms
long tasks become many sub-50ms tasks; each contributes 0ms to TBT
SI
50–200ms
yielding between tasks allows paint frames to execute sooner
Risk: Low
logic is unchanged — only the scheduling is modified
How it works
01
Identify long tasks with DevTools
Open Chrome DevTools → Performance. Record a page load and look for red-flagged tasks in the Main thread lane. Any bar longer than 50 ms with a red triangle is a long task. Lighthouse also lists them under ‘Avoid long main-thread tasks’.
02
Yield with scheduler.yield()
await scheduler.yield()(Chrome 115+) pauses the current task and re-queues the remainder with high priority, giving the browser a frame to handle input before continuing. It is the lowest-latency yield mechanism available.03
Use setTimeout(fn, 0) as a universal fallback
setTimeout(callback, 0)defers execution to a new task, giving the browser a chance to process input between chunks. Wrap large loops or sequential processing steps insetTimeoutcalls to break them across multiple tasks.04
Use requestIdleCallback for non-critical work
requestIdleCallbackruns work only during browser idle periods — analytics pings, prefetching, reporting. It never runs during active rendering or input handling, making it the safest way to defer work that does not need to complete by a specific time.
Limitations
- Yielding introduces asynchrony — code that previously ran synchronously now requires async/await, which changes error handling and execution order.
- scheduler.yield() is Chrome 115+ only — always provide a setTimeout fallback for full browser support.
- Breaking up tasks has no effect on the total CPU work done — it only changes when that work happens, not how much. If the total work is excessive, removal or optimisation is also needed.
Common Mistakes
Yielding after every single item in a large loop
Yielding introduces overhead — a setTimeout per iteration of a 10 000-item loop creates 10 000 tasks. Yield between batches (every 50–100 items), not between individual items.
Deferring everything to requestIdleCallback
requestIdleCallback only runs during idle periods — on a busy page it may never fire, or fire too late. Use it only for genuinely low-priority, non-time-sensitive work like prefetching or analytics queuing.
Need help applying this to your site?
Targeted fixes, not guesswork. We identify which techniques move the needle for your specific WordPress stack.
Start an Engagement