[{"data":1,"prerenderedAt":1650},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F":1641},{"id":4,"title":5,"body":6,"description":1623,"extension":1624,"meta":1625,"navigation":440,"path":1636,"seo":1637,"stem":1639,"__hash__":1640},"content\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Findex.md","Optimizing INP with scheduler.yield()",{"type":7,"value":8,"toc":1613},"minimark",[9,14,24,36,67,198,203,212,255,262,391,399,403,414,561,580,584,600,607,611,625,813,832,838,993,1010,1022,1026,1032,1078,1102,1106,1118,1134,1379,1398,1417,1421,1424,1549,1562,1566,1598,1603,1606,1609],[10,11,13],"h1",{"id":12},"optimizing-inp-with-scheduleryield-breaking-up-long-tasks-to-stay-under-200ms","Optimizing INP with scheduler.yield(): Breaking Up Long Tasks to Stay Under 200ms",[15,16,17,18,23],"p",{},"This guide extends the interactivity work in ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement"," with the modern main-thread scheduling APIs that finally make cooperative yielding ergonomic.",[15,25,26,27,31,32,35],{},"Interaction to Next Paint (INP) regresses for one dominant reason: a single JavaScript task occupies the main thread long enough that the browser cannot run an event callback or paint the next frame on time. Any task that runs longer than ",[28,29,30],"code",{},"50ms"," is, by definition, a long task, and a long task that overlaps a user interaction pushes that interaction's total latency past the ",[28,33,34],{},"200ms"," \"good\" boundary. Because INP reports the worst (or near-worst) interaction across the whole page visit, a single unsplit batch buried in one handler is enough to fail the metric in the field even when the median interaction is fast. The fix is not to do less work in aggregate — it is to slice that work into chunks shorter than the long-task budget and hand control back to the browser between chunks, so a queued click or keystroke can jump ahead of the remaining work and be processed within budget.",[15,37,38,39,42,43,46,47,50,51,54,55,58,59,62,63,66],{},"The naive way to yield is ",[28,40,41],{},"setTimeout(0)",", which works but is blunt: the continuation lands at the back of the task queue, the delay is clamped, and the browser has no signal about how urgent the resumed work is. The ",[28,44,45],{},"scheduler.yield()"," API (shipping in Chrome 129+ and Firefox 142+) and the broader Prioritized Task Scheduling API (",[28,48,49],{},"scheduler.postTask()",") replace that guesswork with explicit, queue-aware scheduling: yield continuations resume ahead of fresh tasks, and ",[28,52,53],{},"postTask"," lets you label work as ",[28,56,57],{},"user-blocking",", ",[28,60,61],{},"user-visible",", or ",[28,64,65],{},"background"," so the browser orders it against rendering correctly. This guide walks the full diagnostic loop — baseline the interaction, isolate which INP phase dominates, insert yield points where they actually help, and lock the result behind a CI budget — rather than scattering yields and hoping.",[15,68,69],{},[70,71,78,79,78,83,78,87,78,97,78,104,78,109,78,118,78,124,78,130,78,135,78,140,78,144,78,147,78,150,78,153,78,156,78,159,78,164,78,167,78,170,78,173,78,176,78,182,78,187,78,189,78,194,78],"svg",{"xmlns":72,"viewBox":73,"width":74,"role":75,"ariaLabel":76,"style":77},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 320","100%","img","A single 220ms long task split into yielded chunks under the 50ms budget, with input handled between chunks","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:#001d3d"," ",[80,81,82],"title",{},"Splitting a long task with scheduler.yield()",[84,85,86],"desc",{},"Before: one 220ms task blocks input. After: five chunks under 50ms each, with a yield point where the queued click runs.",[88,89],"rect",{"x":90,"y":90,"width":91,"height":92,"rx":93,"fill":94,"stroke":95,"style":96},"1","758","318","10","none","currentColor","stroke-opacity:0.18",[98,99,103],"text",{"x":100,"y":101,"fill":95,"style":102},"24","38","font-size:18px;font-weight:700","Long task vs. yielded chunks (budget 50ms)",[98,105,108],{"x":100,"y":106,"fill":95,"style":107},"74","font-size:14px;font-weight:600","Before",[88,110],{"x":100,"y":111,"width":112,"height":113,"rx":114,"fill":115,"stroke":116,"style":117},"86","500","40","5","#ffc300","#b8860b","fill-opacity:0.22",[98,119,123],{"x":120,"y":121,"fill":95,"style":122},"274","111","font-size:14px;font-weight:600;text-anchor:middle","one task: 220ms (blocks input)",[88,125],{"x":126,"y":111,"width":127,"height":113,"rx":114,"fill":128,"stroke":128,"style":129},"540","80","#0466c8","fill-opacity:0.14",[98,131,134],{"x":132,"y":121,"fill":95,"style":133},"580","font-size:14px;text-anchor:middle","click",[98,136,139],{"x":126,"y":137,"fill":95,"style":138},"146","font-size:12px","input wait → INP fail",[98,141,143],{"x":100,"y":142,"fill":95,"style":107},"188","After",[88,145],{"x":100,"y":146,"width":111,"height":113,"rx":114,"fill":128,"stroke":128,"style":129},"200",[88,148],{"x":149,"y":146,"width":111,"height":113,"rx":114,"fill":128,"stroke":128,"style":129},"120",[88,151],{"x":152,"y":146,"width":111,"height":113,"rx":114,"fill":128,"stroke":128,"style":129},"246",[88,154],{"x":155,"y":146,"width":111,"height":113,"rx":114,"fill":128,"stroke":128,"style":129},"342",[88,157],{"x":158,"y":146,"width":111,"height":113,"rx":114,"fill":128,"stroke":128,"style":129},"468",[98,160,163],{"x":161,"y":162,"fill":95,"style":133},"67","225","44ms",[98,165,163],{"x":166,"y":162,"fill":95,"style":133},"163",[98,168,163],{"x":169,"y":162,"fill":95,"style":133},"289",[98,171,163],{"x":172,"y":162,"fill":95,"style":133},"385",[98,174,163],{"x":175,"y":162,"fill":95,"style":133},"511",[177,178],"line",{"x1":179,"y1":180,"x2":179,"y2":152,"stroke":116,"style":181},"216","194","stroke-width:2",[98,183,186],{"x":184,"y":185,"fill":95,"style":107},"222","214","yield → click runs here",[177,188],{"x1":155,"y1":180,"x2":155,"y2":152,"stroke":116,"style":181},[98,190,193],{"x":100,"y":191,"fill":95,"style":192},"284","font-size:14px","Each chunk stays under the 50ms budget; queued input runs at a yield point.",[98,195,197],{"x":100,"y":196,"fill":95,"style":192},"306","scheduler.yield() re-queues continuation ahead of fresh tasks, unlike setTimeout(0).",[199,200,202],"h2",{"id":201},"_1-environment-setup-and-feature-detection","1. Environment Setup and Feature Detection",[15,204,205,206,208,209,211],{},"Confirm the runtime first. ",[28,207,45],{}," requires Chromium 129+ or Firefox 142+; ",[28,210,49],{}," has been stable in Chromium since 94 and is available in Firefox 142+. Safari ships neither at the time of writing, so every production path needs a fallback. Install the official polyfill so the API surface is uniform across browsers and your code paths do not branch on every call:",[213,214,219],"pre",{"className":215,"code":216,"language":217,"meta":218,"style":218},"language-bash shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","# trade-off: the polyfill emulates postTask\u002Fyield via setTimeout + a priority queue,\n# so it restores the API shape but NOT native scheduling integration — skip it if you\n# already gate all calls behind feature detection and only target Chromium 129+.\nnpm install scheduler-polyfill\n","bash","",[28,220,221,229,235,241],{"__ignoreMap":218},[222,223,225],"span",{"class":177,"line":224},1,[222,226,228],{"class":227},"sIIH1","# trade-off: the polyfill emulates postTask\u002Fyield via setTimeout + a priority queue,\n",[222,230,232],{"class":177,"line":231},2,[222,233,234],{"class":227},"# so it restores the API shape but NOT native scheduling integration — skip it if you\n",[222,236,238],{"class":177,"line":237},3,[222,239,240],{"class":227},"# already gate all calls behind feature detection and only target Chromium 129+.\n",[222,242,244,248,252],{"class":177,"line":243},4,[222,245,247],{"class":246},"seIZK","npm",[222,249,251],{"class":250},"s-_DF"," install",[222,253,254],{"class":250}," scheduler-polyfill\n",[15,256,257,258,261],{},"Gate behavior on capability, not on a browser string. A single ",[28,259,260],{},"yieldToMain()"," helper centralizes the decision so the rest of the codebase stays declarative:",[213,263,267],{"className":264,"code":265,"language":266,"meta":218,"style":218},"language-javascript shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u002F\u002F One yield helper, used everywhere. Centralizing the fallback keeps call sites clean.\nfunction yieldToMain() {\n  if ('scheduler' in window && 'yield' in window.scheduler) {\n    return window.scheduler.yield();\n  }\n  \u002F\u002F trade-off: setTimeout(0) yields but drops to the back of the task queue, so a\n  \u002F\u002F burst of other tasks can starve your continuation — acceptable only as a fallback.\n  return new Promise((resolve) => setTimeout(resolve, 0));\n}\n","javascript",[28,268,269,274,288,316,330,336,342,348,385],{"__ignoreMap":218},[222,270,271],{"class":177,"line":224},[222,272,273],{"class":227},"\u002F\u002F One yield helper, used everywhere. Centralizing the fallback keeps call sites clean.\n",[222,275,276,280,284],{"class":177,"line":231},[222,277,279],{"class":278},"sP5qI","function",[222,281,283],{"class":282},"ssM3C"," yieldToMain",[222,285,287],{"class":286},"syybb","() {\n",[222,289,290,293,296,299,302,305,308,311,313],{"class":177,"line":237},[222,291,292],{"class":278},"  if",[222,294,295],{"class":286}," (",[222,297,298],{"class":250},"'scheduler'",[222,300,301],{"class":278}," in",[222,303,304],{"class":286}," window ",[222,306,307],{"class":278},"&&",[222,309,310],{"class":250}," 'yield'",[222,312,301],{"class":278},[222,314,315],{"class":286}," window.scheduler) {\n",[222,317,318,321,324,327],{"class":177,"line":243},[222,319,320],{"class":278},"    return",[222,322,323],{"class":286}," window.scheduler.",[222,325,326],{"class":282},"yield",[222,328,329],{"class":286},"();\n",[222,331,333],{"class":177,"line":332},5,[222,334,335],{"class":286},"  }\n",[222,337,339],{"class":177,"line":338},6,[222,340,341],{"class":227},"  \u002F\u002F trade-off: setTimeout(0) yields but drops to the back of the task queue, so a\n",[222,343,345],{"class":177,"line":344},7,[222,346,347],{"class":227},"  \u002F\u002F burst of other tasks can starve your continuation — acceptable only as a fallback.\n",[222,349,351,354,357,361,364,367,370,373,376,379,382],{"class":177,"line":350},8,[222,352,353],{"class":278},"  return",[222,355,356],{"class":278}," new",[222,358,360],{"class":359},"sf6mN"," Promise",[222,362,363],{"class":286},"((",[222,365,366],{"class":246},"resolve",[222,368,369],{"class":286},") ",[222,371,372],{"class":278},"=>",[222,374,375],{"class":282}," setTimeout",[222,377,378],{"class":286},"(resolve, ",[222,380,381],{"class":359},"0",[222,383,384],{"class":286},"));\n",[222,386,388],{"class":177,"line":387},9,[222,389,390],{"class":286},"}\n",[15,392,393,394,398],{},"Pair this with the interactivity baseline from ",[19,395,397],{"href":396},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002F","Optimizing First Input Delay (FID)",", which establishes the main-thread hygiene that yielding builds on.",[199,400,402],{"id":401},"_2-capture-a-baseline-with-long-task-and-event-timing-data","2. Capture a Baseline with Long-Task and Event-Timing Data",[15,404,405,406,409,410,413],{},"You cannot fix what you have not located. Record real-user INP with the ",[28,407,408],{},"web-vitals"," attribution build, which splits each interaction into input delay, processing duration, and presentation delay, then correlate the worst interactions against ",[28,411,412],{},"PerformanceLongTaskTiming"," entries to find the blocking script.",[213,415,417],{"className":264,"code":416,"language":266,"meta":218,"style":218},"import { onINP } from 'web-vitals\u002Fattribution';\n\nonINP(({ value, attribution }) => {\n  \u002F\u002F attribution.longAnimationFrameEntries pinpoints the blocking script + its duration.\n  navigator.sendBeacon('\u002Frum\u002Finp', JSON.stringify({\n    value,\n    phase: {\n      input: attribution.inputDelay,\n      processing: attribution.processingDuration,\n      presentation: attribution.presentationDelay,\n    },\n    target: attribution.interactionTarget,\n  }));\n  \u002F\u002F trade-off: reportAllChanges:true gives finer data but more beacons — leave it off\n  \u002F\u002F in high-traffic production and rely on the p75 final value instead.\n});\n",[28,418,419,436,442,466,471,499,504,509,514,519,525,531,537,543,549,555],{"__ignoreMap":218},[222,420,421,424,427,430,433],{"class":177,"line":224},[222,422,423],{"class":278},"import",[222,425,426],{"class":286}," { onINP } ",[222,428,429],{"class":278},"from",[222,431,432],{"class":250}," 'web-vitals\u002Fattribution'",[222,434,435],{"class":286},";\n",[222,437,438],{"class":177,"line":231},[222,439,441],{"emptyLinePlaceholder":440},true,"\n",[222,443,444,447,450,453,455,458,461,463],{"class":177,"line":237},[222,445,446],{"class":282},"onINP",[222,448,449],{"class":286},"(({ ",[222,451,452],{"class":246},"value",[222,454,58],{"class":286},[222,456,457],{"class":246},"attribution",[222,459,460],{"class":286}," }) ",[222,462,372],{"class":278},[222,464,465],{"class":286}," {\n",[222,467,468],{"class":177,"line":243},[222,469,470],{"class":227},"  \u002F\u002F attribution.longAnimationFrameEntries pinpoints the blocking script + its duration.\n",[222,472,473,476,479,482,485,487,490,493,496],{"class":177,"line":332},[222,474,475],{"class":286},"  navigator.",[222,477,478],{"class":282},"sendBeacon",[222,480,481],{"class":286},"(",[222,483,484],{"class":250},"'\u002Frum\u002Finp'",[222,486,58],{"class":286},[222,488,489],{"class":359},"JSON",[222,491,492],{"class":286},".",[222,494,495],{"class":282},"stringify",[222,497,498],{"class":286},"({\n",[222,500,501],{"class":177,"line":338},[222,502,503],{"class":286},"    value,\n",[222,505,506],{"class":177,"line":344},[222,507,508],{"class":286},"    phase: {\n",[222,510,511],{"class":177,"line":350},[222,512,513],{"class":286},"      input: attribution.inputDelay,\n",[222,515,516],{"class":177,"line":387},[222,517,518],{"class":286},"      processing: attribution.processingDuration,\n",[222,520,522],{"class":177,"line":521},10,[222,523,524],{"class":286},"      presentation: attribution.presentationDelay,\n",[222,526,528],{"class":177,"line":527},11,[222,529,530],{"class":286},"    },\n",[222,532,534],{"class":177,"line":533},12,[222,535,536],{"class":286},"    target: attribution.interactionTarget,\n",[222,538,540],{"class":177,"line":539},13,[222,541,542],{"class":286},"  }));\n",[222,544,546],{"class":177,"line":545},14,[222,547,548],{"class":227},"  \u002F\u002F trade-off: reportAllChanges:true gives finer data but more beacons — leave it off\n",[222,550,552],{"class":177,"line":551},15,[222,553,554],{"class":227},"  \u002F\u002F in high-traffic production and rely on the p75 final value instead.\n",[222,556,558],{"class":177,"line":557},16,[222,559,560],{"class":286},"});\n",[15,562,563,564,567,568,570,571,575,576,492],{},"For lab capture, open the Performance panel under ",[28,565,566],{},"4x"," CPU throttling, run the interaction, and read the Long Tasks track. Anything wider than ",[28,569,30],{}," that overlaps your interaction is a yield candidate. Switch to the ",[572,573,574],"strong",{},"Interactions"," track to see the recorded INP for the exact action, then drill into the flame chart beneath it to see which function dominates the long task. Record the baseline number — the worst interaction's INP and the width of its longest task — because every later step is judged against it. The deeper workflow for replaying and ranking interactions lives in ",[19,577,579],{"href":578},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002F","profiling event handlers for INP",[199,581,583],{"id":582},"_3-isolate-the-dominant-bottleneck","3. Isolate the Dominant Bottleneck",[15,585,586,587,591,592,595,596,599],{},"INP latency has three additive parts (deconstructed below), so before adding yield points decide which part dominates. If input delay is large, the main thread was already busy when the interaction arrived — yield ",[588,589,590],"em",{},"earlier",", inside whatever task was running at click time. If processing duration is large, your own handler is the long task — yield ",[588,593,594],{},"inside"," it. If presentation delay is large, yielding will not help; that is a rendering\u002Fpaint problem (large DOM, expensive style recalc) better addressed with ",[28,597,598],{},"content-visibility"," and reduced commit size.",[15,601,602,603,606],{},"Use the attribution phase breakdown from step 2 to label each slow interaction. Yielding is the correct tool only for the input-delay and processing-duration phases; spending effort adding yield points to a presentation-bound interaction wastes time and can even hurt, because the extra event-loop turns delay the paint you were trying to speed up. A quick rule of thumb: if the flame chart shows your script running when the click arrives, the problem is input delay; if it shows your handler running for a long stretch ",[588,604,605],{},"after"," the click, it is processing duration; if the gap sits between your handler finishing and the next paint, it is presentation delay. Only the first two are scheduling problems.",[199,608,610],{"id":609},"_4-apply-the-fix-yield-points-inside-long-work","4. Apply the Fix: Yield Points Inside Long Work",[15,612,613,614,617,618,621,622,624],{},"The core pattern is the ",[572,615,616],{},"await-yield loop",": process a batch of work, check the elapsed time against the budget, and ",[28,619,620],{},"await yieldToMain()"," once the budget is exhausted. Yielding by item count is fragile because item cost varies; yield by elapsed time so each chunk stays under ",[28,623,30],{}," regardless of payload.",[213,626,628],{"className":264,"code":627,"language":266,"meta":218,"style":218},"async function processInChunks(items, handleItem) {\n  let deadline = performance.now() + 50; \u002F\u002F 50ms long-task budget per chunk\n  for (let i = 0; i \u003C items.length; i++) {\n    handleItem(items[i]);\n    if (performance.now() >= deadline) {\n      await yieldToMain();          \u002F\u002F hands control back; queued input runs now\n      deadline = performance.now() + 50; \u002F\u002F reset budget after resuming\n    }\n  }\n  \u002F\u002F trade-off: yielding by elapsed time adds a performance.now() call per item; for\n  \u002F\u002F tight numeric loops over millions of cheap items, batch the time check every Nth\n  \u002F\u002F iteration or move the whole job to a worker instead.\n}\n",[28,629,630,654,686,724,732,750,763,785,790,794,799,804,809],{"__ignoreMap":218},[222,631,632,635,638,641,643,646,648,651],{"class":177,"line":224},[222,633,634],{"class":278},"async",[222,636,637],{"class":278}," function",[222,639,640],{"class":282}," processInChunks",[222,642,481],{"class":286},[222,644,645],{"class":246},"items",[222,647,58],{"class":286},[222,649,650],{"class":246},"handleItem",[222,652,653],{"class":286},") {\n",[222,655,656,659,662,665,668,671,674,677,680,683],{"class":177,"line":231},[222,657,658],{"class":278},"  let",[222,660,661],{"class":286}," deadline ",[222,663,664],{"class":278},"=",[222,666,667],{"class":286}," performance.",[222,669,670],{"class":282},"now",[222,672,673],{"class":286},"() ",[222,675,676],{"class":278},"+",[222,678,679],{"class":359}," 50",[222,681,682],{"class":286},"; ",[222,684,685],{"class":227},"\u002F\u002F 50ms long-task budget per chunk\n",[222,687,688,691,693,696,699,701,704,707,710,713,716,719,722],{"class":177,"line":237},[222,689,690],{"class":278},"  for",[222,692,295],{"class":286},[222,694,695],{"class":278},"let",[222,697,698],{"class":286}," i ",[222,700,664],{"class":278},[222,702,703],{"class":359}," 0",[222,705,706],{"class":286},"; i ",[222,708,709],{"class":278},"\u003C",[222,711,712],{"class":286}," items.",[222,714,715],{"class":359},"length",[222,717,718],{"class":286},"; i",[222,720,721],{"class":278},"++",[222,723,653],{"class":286},[222,725,726,729],{"class":177,"line":243},[222,727,728],{"class":282},"    handleItem",[222,730,731],{"class":286},"(items[i]);\n",[222,733,734,737,740,742,744,747],{"class":177,"line":332},[222,735,736],{"class":278},"    if",[222,738,739],{"class":286}," (performance.",[222,741,670],{"class":282},[222,743,673],{"class":286},[222,745,746],{"class":278},">=",[222,748,749],{"class":286}," deadline) {\n",[222,751,752,755,757,760],{"class":177,"line":338},[222,753,754],{"class":278},"      await",[222,756,283],{"class":282},[222,758,759],{"class":286},"();          ",[222,761,762],{"class":227},"\u002F\u002F hands control back; queued input runs now\n",[222,764,765,768,770,772,774,776,778,780,782],{"class":177,"line":344},[222,766,767],{"class":286},"      deadline ",[222,769,664],{"class":278},[222,771,667],{"class":286},[222,773,670],{"class":282},[222,775,673],{"class":286},[222,777,676],{"class":278},[222,779,679],{"class":359},[222,781,682],{"class":286},[222,783,784],{"class":227},"\u002F\u002F reset budget after resuming\n",[222,786,787],{"class":177,"line":350},[222,788,789],{"class":286},"    }\n",[222,791,792],{"class":177,"line":387},[222,793,335],{"class":286},[222,795,796],{"class":177,"line":521},[222,797,798],{"class":227},"  \u002F\u002F trade-off: yielding by elapsed time adds a performance.now() call per item; for\n",[222,800,801],{"class":177,"line":527},[222,802,803],{"class":227},"  \u002F\u002F tight numeric loops over millions of cheap items, batch the time check every Nth\n",[222,805,806],{"class":177,"line":533},[222,807,808],{"class":227},"  \u002F\u002F iteration or move the whole job to a worker instead.\n",[222,810,811],{"class":177,"line":539},[222,812,390],{"class":286},[15,814,815,816,818,819,821,822,824,825,827,828,831],{},"Why this beats ",[28,817,41],{},": when you ",[28,820,41],{},", the continuation is appended to the back of the task queue, so any task already queued — including other yielded continuations or low-priority work — runs first, and your \"0ms\" delay is realistically clamped and reordered. ",[28,823,45],{}," instead returns a promise whose continuation is scheduled at a higher priority than fresh ",[28,826,53],{}," tasks, so your loop resumes ",[588,829,830],{},"promptly after"," pending user input is handled, not after an unbounded backlog. In practice this turns \"yield and hope\" into \"yield and resume,\" which is what keeps a long batch from ballooning total wall-clock time.",[15,833,834,835,837],{},"For work that is not a tight loop — independent units like hydrating widgets, warming caches, or prefetching — schedule each with ",[28,836,49],{}," and an explicit priority instead of hand-rolling a queue:",[213,839,841],{"className":264,"code":840,"language":266,"meta":218,"style":218},"function schedule(fn, priority = 'user-visible') {\n  if ('scheduler' in window && 'postTask' in window.scheduler) {\n    \u002F\u002F priorities: 'user-blocking' (highest), 'user-visible' (default), 'background'\n    return window.scheduler.postTask(fn, { priority });\n  }\n  \u002F\u002F trade-off: the fallback ignores priority entirely, so on Safari a 'background'\n  \u002F\u002F job competes equally with 'user-blocking' work — keep fallback jobs small.\n  return Promise.resolve().then(fn);\n}\n\nschedule(() => renderAboveFold(), 'user-blocking');   \u002F\u002F must finish before next paint\nschedule(() => prefetchNextRoute(), 'background');     \u002F\u002F can wait for idle\n",[28,842,843,868,889,894,905,909,914,919,938,942,946,971],{"__ignoreMap":218},[222,844,845,847,850,852,855,857,860,863,866],{"class":177,"line":224},[222,846,279],{"class":278},[222,848,849],{"class":282}," schedule",[222,851,481],{"class":286},[222,853,854],{"class":246},"fn",[222,856,58],{"class":286},[222,858,859],{"class":246},"priority",[222,861,862],{"class":278}," =",[222,864,865],{"class":250}," 'user-visible'",[222,867,653],{"class":286},[222,869,870,872,874,876,878,880,882,885,887],{"class":177,"line":231},[222,871,292],{"class":278},[222,873,295],{"class":286},[222,875,298],{"class":250},[222,877,301],{"class":278},[222,879,304],{"class":286},[222,881,307],{"class":278},[222,883,884],{"class":250}," 'postTask'",[222,886,301],{"class":278},[222,888,315],{"class":286},[222,890,891],{"class":177,"line":237},[222,892,893],{"class":227},"    \u002F\u002F priorities: 'user-blocking' (highest), 'user-visible' (default), 'background'\n",[222,895,896,898,900,902],{"class":177,"line":243},[222,897,320],{"class":278},[222,899,323],{"class":286},[222,901,53],{"class":282},[222,903,904],{"class":286},"(fn, { priority });\n",[222,906,907],{"class":177,"line":332},[222,908,335],{"class":286},[222,910,911],{"class":177,"line":338},[222,912,913],{"class":227},"  \u002F\u002F trade-off: the fallback ignores priority entirely, so on Safari a 'background'\n",[222,915,916],{"class":177,"line":344},[222,917,918],{"class":227},"  \u002F\u002F job competes equally with 'user-blocking' work — keep fallback jobs small.\n",[222,920,921,923,925,927,929,932,935],{"class":177,"line":350},[222,922,353],{"class":278},[222,924,360],{"class":359},[222,926,492],{"class":286},[222,928,366],{"class":282},[222,930,931],{"class":286},"().",[222,933,934],{"class":282},"then",[222,936,937],{"class":286},"(fn);\n",[222,939,940],{"class":177,"line":387},[222,941,390],{"class":286},[222,943,944],{"class":177,"line":521},[222,945,441],{"emptyLinePlaceholder":440},[222,947,948,951,954,956,959,962,965,968],{"class":177,"line":527},[222,949,950],{"class":282},"schedule",[222,952,953],{"class":286},"(() ",[222,955,372],{"class":278},[222,957,958],{"class":282}," renderAboveFold",[222,960,961],{"class":286},"(), ",[222,963,964],{"class":250},"'user-blocking'",[222,966,967],{"class":286},");   ",[222,969,970],{"class":227},"\u002F\u002F must finish before next paint\n",[222,972,973,975,977,979,982,984,987,990],{"class":177,"line":533},[222,974,950],{"class":282},[222,976,953],{"class":286},[222,978,372],{"class":278},[222,980,981],{"class":282}," prefetchNextRoute",[222,983,961],{"class":286},[222,985,986],{"class":250},"'background'",[222,988,989],{"class":286},");     ",[222,991,992],{"class":227},"\u002F\u002F can wait for idle\n",[15,994,995,997,998,1000,1001,1003,1004,1006,1007,1009],{},[28,996,57],{}," work is scheduled ahead of rendering and should be reserved for the handful of tasks that must complete before the next frame — committing the visible result of an interaction, for example. ",[28,999,61],{}," (the default) covers most work the user will notice but that can tolerate a frame of latency. ",[28,1002,65],{}," work yields to nearly everything and is the right home for analytics warm-up, log flushing, and speculative prefetch; scheduling those at ",[28,1005,65],{}," keeps them from ever competing with an interaction. The practical discipline is to pick the lowest priority a task can tolerate rather than defaulting everything to ",[28,1008,61],{},", because over-prioritizing reintroduces the contention you are trying to remove.",[15,1011,1012,1013,1017,1018,1021],{},"Truly CPU-bound jobs (parsing, diffing, image work) should leave the main thread entirely — see ",[19,1014,1016],{"href":1015},"\u002Fcore-web-vitals-measurement\u002Foffloading-work-to-web-workers-with-comlink\u002F","offloading work to web workers with Comlink",", since no amount of yielding makes a 400ms parse cheap, it only makes it interruptible. Yielding is the right tool when the total work is acceptable but its ",[588,1019,1020],{},"shape"," is wrong (one long block instead of many short ones); a worker is the right tool when the total work itself is too expensive to run alongside rendering at all.",[199,1023,1025],{"id":1024},"deconstructing-inp-input-delay-processing-presentation-delay","Deconstructing INP: Input Delay + Processing + Presentation Delay",[15,1027,1028,1029,1031],{},"INP for a given interaction is the sum of three measurable phases, and each has its own diagnostic and its own budget against the ",[28,1030,34],{}," total:",[1033,1034,1035,1049,1068],"ul",{},[1036,1037,1038,1041,1042,1044,1045,1048],"li",{},[572,1039,1040],{},"Input delay"," — time from the user's action until the first event listener begins running. Caused by the main thread being busy with an unrelated task when the interaction arrives. Target well under ",[28,1043,30],{},". The cure is yielding ",[588,1046,1047],{},"in the busy task",", not in the handler.",[1036,1050,1051,1054,1055,58,1057,58,1060,1063,1064,1067],{},[572,1052,1053],{},"Processing duration"," — time spent running all event listeners for the interaction (the ",[28,1056,134],{},[28,1058,1059],{},"input",[28,1061,1062],{},"change",", plus framework-internal work like state updates). This is usually the largest phase in JavaScript-heavy apps. Target ",[28,1065,1066],{},"\u003C 100ms",". The cure is splitting the handler with the await-yield loop and deferring non-urgent work.",[1036,1069,1070,1073,1074,1077],{},[572,1071,1072],{},"Presentation delay"," — time from listeners finishing until the browser paints the next frame reflecting the change. Caused by large style\u002Flayout\u002Fpaint cost. Target ",[28,1075,1076],{},"\u003C 50ms",". The cure is reducing commit size, not scheduling.",[15,1079,1080,1081,1084,1085,1088,1089,1092,1093,1096,1097,1101],{},"A common failure is a handler that measures ",[28,1082,1083],{},"40ms"," of processing but ships ",[28,1086,1087],{},"300ms"," INP because a ",[28,1090,1091],{},"220ms"," task was already mid-flight at click time, inflating input delay. The phase breakdown tells you to yield in the ",[588,1094,1095],{},"upstream"," task. Cross-reference this model with ",[19,1098,1100],{"href":1099},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Fimproving-inp-for-complex-single-page-applications\u002F","improving INP for complex single page applications",", where router transitions and hydration commonly inflate the input-delay phase.",[199,1103,1105],{"id":1104},"advanced-diagnostics-and-edge-case-failure-modes","Advanced Diagnostics and Edge-Case Failure Modes",[15,1107,1108,1111,1112,1114,1115,1117],{},[572,1109,1110],{},"Yield starvation under continuation pressure."," If you sprinkle ",[28,1113,45],{}," across many concurrent loops, their continuations contend. Because yield continuations are prioritized over fresh tasks, a flood of them can still delay genuinely new ",[28,1116,57],{}," work. Prefer one coordinated queue over dozens of independent yielding loops.",[15,1119,1120,1126,1127,1129,1130,1133],{},[572,1121,1122,1125],{},[28,1123,1124],{},"AbortController"," to cancel stale work."," When a user re-interacts (types another character, clicks a different tab), the in-flight chunked job is now wasted. ",[28,1128,49],{}," accepts an ",[28,1131,1132],{},"AbortSignal","; pass one and abort it on the next interaction so you never burn budget computing a result nobody will see.",[213,1135,1137],{"className":264,"code":1136,"language":266,"meta":218,"style":218},"let controller = new AbortController();\nfunction startWork(items) {\n  controller.abort();                 \u002F\u002F cancel the previous, now-stale run\n  controller = new AbortController();\n  const signal = controller.signal;\n  return window.scheduler.postTask(async () => {\n    let deadline = performance.now() + 50;\n    for (const item of items) {\n      if (signal.aborted) return;     \u002F\u002F stop the moment a newer interaction supersedes us\n      process(item);\n      if (performance.now() >= deadline) {\n        await window.scheduler.yield();\n        deadline = performance.now() + 50;\n      }\n    }\n  }, { signal, priority: 'user-visible' });\n  \u002F\u002F trade-off: abort-on-reinteraction is ideal for search\u002Ffilter; for \"save\" or\n  \u002F\u002F payment work, aborting a partially-applied mutation can corrupt state — never\n  \u002F\u002F cancel non-idempotent jobs.\n}\n",[28,1138,1139,1155,1168,1182,1195,1208,1227,1248,1267,1284,1292,1306,1317,1336,1341,1345,1356,1362,1368,1374],{"__ignoreMap":218},[222,1140,1141,1143,1146,1148,1150,1153],{"class":177,"line":224},[222,1142,695],{"class":278},[222,1144,1145],{"class":286}," controller ",[222,1147,664],{"class":278},[222,1149,356],{"class":278},[222,1151,1152],{"class":282}," AbortController",[222,1154,329],{"class":286},[222,1156,1157,1159,1162,1164,1166],{"class":177,"line":231},[222,1158,279],{"class":278},[222,1160,1161],{"class":282}," startWork",[222,1163,481],{"class":286},[222,1165,645],{"class":246},[222,1167,653],{"class":286},[222,1169,1170,1173,1176,1179],{"class":177,"line":237},[222,1171,1172],{"class":286},"  controller.",[222,1174,1175],{"class":282},"abort",[222,1177,1178],{"class":286},"();                 ",[222,1180,1181],{"class":227},"\u002F\u002F cancel the previous, now-stale run\n",[222,1183,1184,1187,1189,1191,1193],{"class":177,"line":243},[222,1185,1186],{"class":286},"  controller ",[222,1188,664],{"class":278},[222,1190,356],{"class":278},[222,1192,1152],{"class":282},[222,1194,329],{"class":286},[222,1196,1197,1200,1203,1205],{"class":177,"line":332},[222,1198,1199],{"class":278},"  const",[222,1201,1202],{"class":359}," signal",[222,1204,862],{"class":278},[222,1206,1207],{"class":286}," controller.signal;\n",[222,1209,1210,1212,1214,1216,1218,1220,1223,1225],{"class":177,"line":338},[222,1211,353],{"class":278},[222,1213,323],{"class":286},[222,1215,53],{"class":282},[222,1217,481],{"class":286},[222,1219,634],{"class":278},[222,1221,1222],{"class":286}," () ",[222,1224,372],{"class":278},[222,1226,465],{"class":286},[222,1228,1229,1232,1234,1236,1238,1240,1242,1244,1246],{"class":177,"line":344},[222,1230,1231],{"class":278},"    let",[222,1233,661],{"class":286},[222,1235,664],{"class":278},[222,1237,667],{"class":286},[222,1239,670],{"class":282},[222,1241,673],{"class":286},[222,1243,676],{"class":278},[222,1245,679],{"class":359},[222,1247,435],{"class":286},[222,1249,1250,1253,1255,1258,1261,1264],{"class":177,"line":350},[222,1251,1252],{"class":278},"    for",[222,1254,295],{"class":286},[222,1256,1257],{"class":278},"const",[222,1259,1260],{"class":359}," item",[222,1262,1263],{"class":278}," of",[222,1265,1266],{"class":286}," items) {\n",[222,1268,1269,1272,1275,1278,1281],{"class":177,"line":387},[222,1270,1271],{"class":278},"      if",[222,1273,1274],{"class":286}," (signal.aborted) ",[222,1276,1277],{"class":278},"return",[222,1279,1280],{"class":286},";     ",[222,1282,1283],{"class":227},"\u002F\u002F stop the moment a newer interaction supersedes us\n",[222,1285,1286,1289],{"class":177,"line":521},[222,1287,1288],{"class":282},"      process",[222,1290,1291],{"class":286},"(item);\n",[222,1293,1294,1296,1298,1300,1302,1304],{"class":177,"line":527},[222,1295,1271],{"class":278},[222,1297,739],{"class":286},[222,1299,670],{"class":282},[222,1301,673],{"class":286},[222,1303,746],{"class":278},[222,1305,749],{"class":286},[222,1307,1308,1311,1313,1315],{"class":177,"line":533},[222,1309,1310],{"class":278},"        await",[222,1312,323],{"class":286},[222,1314,326],{"class":282},[222,1316,329],{"class":286},[222,1318,1319,1322,1324,1326,1328,1330,1332,1334],{"class":177,"line":539},[222,1320,1321],{"class":286},"        deadline ",[222,1323,664],{"class":278},[222,1325,667],{"class":286},[222,1327,670],{"class":282},[222,1329,673],{"class":286},[222,1331,676],{"class":278},[222,1333,679],{"class":359},[222,1335,435],{"class":286},[222,1337,1338],{"class":177,"line":545},[222,1339,1340],{"class":286},"      }\n",[222,1342,1343],{"class":177,"line":551},[222,1344,789],{"class":286},[222,1346,1347,1350,1353],{"class":177,"line":557},[222,1348,1349],{"class":286},"  }, { signal, priority: ",[222,1351,1352],{"class":250},"'user-visible'",[222,1354,1355],{"class":286}," });\n",[222,1357,1359],{"class":177,"line":1358},17,[222,1360,1361],{"class":227},"  \u002F\u002F trade-off: abort-on-reinteraction is ideal for search\u002Ffilter; for \"save\" or\n",[222,1363,1365],{"class":177,"line":1364},18,[222,1366,1367],{"class":227},"  \u002F\u002F payment work, aborting a partially-applied mutation can corrupt state — never\n",[222,1369,1371],{"class":177,"line":1370},19,[222,1372,1373],{"class":227},"  \u002F\u002F cancel non-idempotent jobs.\n",[222,1375,1377],{"class":177,"line":1376},20,[222,1378,390],{"class":286},[15,1380,1381,1384,1385,1388,1389,78,1391,1393,1394,1397],{},[572,1382,1383],{},"Framework scheduling collisions."," React's concurrent scheduler, Vue's ",[28,1386,1387],{},"nextTick",", and Angular zones already run their own task queues. Layering manual ",[28,1390,45],{},[588,1392,594],{}," a React render is counterproductive — let the framework's ",[28,1395,1396],{},"startTransition"," mark the work as non-urgent and reserve raw yielding for plain imperative loops in event handlers and data transforms. The boundary is clean in practice: yield inside the imperative code you wrote (a loop building an index, a transform over a large array), and use the framework primitive for anything that triggers a re-render. Mixing the two — yielding partway through a synchronous render — produces torn UI and unpredictable commit timing.",[15,1399,1400,1403,1404,1406,1407,1409,1410,1412,1413,1416],{},[572,1401,1402],{},"Misreading \"zero\" delay."," Engineers often assume ",[28,1405,41],{}," and ",[28,1408,45],{}," are interchangeable because both \"yield to the event loop.\" They are not: under load, ",[28,1411,41],{}," continuations can be delayed by tens of milliseconds and reordered behind unrelated tasks, so a chunked job that should take ",[28,1414,1415],{},"250ms"," of wall-clock time stretches well past it. Measuring the wall-clock duration of the whole chunked operation — not just per-chunk INP — is the fastest way to see the difference between the two APIs in your own app.",[199,1418,1420],{"id":1419},"validation-and-budgeting-in-ci","Validation and Budgeting in CI",[15,1422,1423],{},"Yielding is only proven by the long-task profile and the field INP after it ships. Assert both. In Lighthouse CI, fail the build when Total Blocking Time regresses, since TBT is the lab proxy that moves when long tasks shrink:",[213,1425,1427],{"className":264,"code":1426,"language":266,"meta":218,"style":218},"\u002F\u002F lighthouserc.js\nmodule.exports = {\n  ci: {\n    assert: {\n      assertions: {\n        \u002F\u002F budget the lab proxy for main-thread blocking\n        'total-blocking-time': ['error', { maxNumericValue: 200 }],\n        'mainthread-work-breakdown': ['warn', { maxNumericValue: 2000 }],\n        'max-potential-fid': ['warn', { maxNumericValue: 130 }],\n      },\n    },\n  },\n};\n\u002F\u002F trade-off: TBT is a lab proxy, not INP itself — a green TBT with red field INP means\n\u002F\u002F the slow interaction happens after load (route change, modal), so pair this with RUM.\n",[28,1428,1429,1434,1448,1453,1458,1463,1468,1487,1504,1520,1525,1529,1534,1539,1544],{"__ignoreMap":218},[222,1430,1431],{"class":177,"line":224},[222,1432,1433],{"class":227},"\u002F\u002F lighthouserc.js\n",[222,1435,1436,1439,1441,1444,1446],{"class":177,"line":231},[222,1437,1438],{"class":359},"module",[222,1440,492],{"class":286},[222,1442,1443],{"class":359},"exports",[222,1445,862],{"class":278},[222,1447,465],{"class":286},[222,1449,1450],{"class":177,"line":237},[222,1451,1452],{"class":286},"  ci: {\n",[222,1454,1455],{"class":177,"line":243},[222,1456,1457],{"class":286},"    assert: {\n",[222,1459,1460],{"class":177,"line":332},[222,1461,1462],{"class":286},"      assertions: {\n",[222,1464,1465],{"class":177,"line":338},[222,1466,1467],{"class":227},"        \u002F\u002F budget the lab proxy for main-thread blocking\n",[222,1469,1470,1473,1476,1479,1482,1484],{"class":177,"line":344},[222,1471,1472],{"class":250},"        'total-blocking-time'",[222,1474,1475],{"class":286},": [",[222,1477,1478],{"class":250},"'error'",[222,1480,1481],{"class":286},", { maxNumericValue: ",[222,1483,146],{"class":359},[222,1485,1486],{"class":286}," }],\n",[222,1488,1489,1492,1494,1497,1499,1502],{"class":177,"line":350},[222,1490,1491],{"class":250},"        'mainthread-work-breakdown'",[222,1493,1475],{"class":286},[222,1495,1496],{"class":250},"'warn'",[222,1498,1481],{"class":286},[222,1500,1501],{"class":359},"2000",[222,1503,1486],{"class":286},[222,1505,1506,1509,1511,1513,1515,1518],{"class":177,"line":387},[222,1507,1508],{"class":250},"        'max-potential-fid'",[222,1510,1475],{"class":286},[222,1512,1496],{"class":250},[222,1514,1481],{"class":286},[222,1516,1517],{"class":359},"130",[222,1519,1486],{"class":286},[222,1521,1522],{"class":177,"line":521},[222,1523,1524],{"class":286},"      },\n",[222,1526,1527],{"class":177,"line":527},[222,1528,530],{"class":286},[222,1530,1531],{"class":177,"line":533},[222,1532,1533],{"class":286},"  },\n",[222,1535,1536],{"class":177,"line":539},[222,1537,1538],{"class":286},"};\n",[222,1540,1541],{"class":177,"line":545},[222,1542,1543],{"class":227},"\u002F\u002F trade-off: TBT is a lab proxy, not INP itself — a green TBT with red field INP means\n",[222,1545,1546],{"class":177,"line":551},[222,1547,1548],{"class":227},"\u002F\u002F the slow interaction happens after load (route change, modal), so pair this with RUM.\n",[15,1550,1551,1552,1554,1555,1557,1558,492],{},"Add a synthetic interaction assertion that fails if any single task during the scripted interaction exceeds the budget — a Puppeteer or Playwright script that performs the interaction while recording ",[28,1553,412],{},", then asserts no entry exceeds ",[28,1556,30],{},". This catches the regression that TBT misses: a slow interaction that only happens after load, on a route change or modal open, where the lab page-load metrics stay green. Run the scripted check in the same pipeline stage as Lighthouse so a single CI gate covers both load-time and interaction-time blocking. Finally, confirm field INP at the p75 in your RUM dashboard before and after the change; treat a deploy as proven only when the field number moves, since synthetic devices rarely reproduce the slow tail of real hardware. The lab gate prevents the regression from merging; the field check confirms the win on real devices. The full CI harness, including custom audits, is covered in ",[19,1559,1561],{"href":1560},"\u002Fcore-web-vitals-measurement\u002Funderstanding-core-web-vitals-thresholds\u002Fbest-lighthouse-ci-setup-for-frontend-pipelines\u002F","the Lighthouse CI setup for frontend pipelines",[199,1563,1565],{"id":1564},"related","Related",[1033,1567,1568,1573,1579,1586,1592],{},[1036,1569,1570,1572],{},[19,1571,397],{"href":396}," — the main-thread hygiene baseline that yielding extends across every interaction.",[1036,1574,1575,1578],{},[19,1576,1577],{"href":1099},"Improving INP for complex single page applications"," — applying phase analysis to router transitions and hydration.",[1036,1580,1581,1585],{},[19,1582,1584],{"href":1583},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fbreaking-up-long-tasks-in-react-event-handlers\u002F","Breaking up long tasks in React event handlers"," — the concrete onClick\u002FonChange failure case and its fixes.",[1036,1587,1588,1591],{},[19,1589,1590],{"href":1015},"Offloading work to web workers with Comlink"," — when work is too heavy to yield and must leave the main thread.",[1036,1593,1594,1597],{},[19,1595,1596],{"href":578},"Profiling event handlers for INP"," — finding and ranking the slow interaction before you schedule around it.",[1599,1600,1602],"script",{"type":1601},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Optimizing INP with scheduler.yield()\",\n  \"description\": \"Break up long tasks with scheduler.yield() and scheduler.postTask() to keep Interaction to Next Paint under 200ms.\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"name\": \"Environment setup and feature detection\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F#1-environment-setup-and-feature-detection\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Capture a baseline with long-task and event-timing data\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F#2-capture-a-baseline-with-long-task-and-event-timing-data\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Isolate the dominant bottleneck\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F#3-isolate-the-dominant-bottleneck\" },\n    { \"@type\": \"HowToStep\", \"name\": \"Apply yield points inside long work\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F#4-apply-the-fix-yield-points-inside-long-work\" }\n  ]\n}\n",[1599,1604,1605],{"type":1601},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Optimizing INP with scheduler.yield(): Breaking Up Long Tasks to Stay Under 200ms\",\n  \"description\": \"Use scheduler.yield() and the Prioritized Task Scheduling API to split long tasks and hold INP under 200ms.\",\n  \"datePublished\": \"2026-06-18\",\n  \"dateModified\": \"2026-06-18\",\n  \"mainEntityOfPage\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F\"\n}\n",[1599,1607,1608],{"type":1601},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    { \"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 2, \"name\": \"Core Web Vitals & Measurement\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"Optimizing INP with scheduler.yield()\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F\" }\n  ]\n}\n",[1610,1611,1612],"style",{},"html pre.shiki code .sIIH1, html code.shiki .sIIH1{--shiki-default:#66707B;--shiki-dark:#66707B;--shiki-light:#66707B}html pre.shiki code .seIZK, html code.shiki .seIZK{--shiki-default:#702C00;--shiki-dark:#702C00;--shiki-light:#702C00}html pre.shiki code .s-_DF, html code.shiki .s-_DF{--shiki-default:#032563;--shiki-dark:#032563;--shiki-light:#032563}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sP5qI, html code.shiki .sP5qI{--shiki-default:#A0111F;--shiki-dark:#A0111F;--shiki-light:#A0111F}html pre.shiki code .ssM3C, html code.shiki .ssM3C{--shiki-default:#622CBC;--shiki-dark:#622CBC;--shiki-light:#622CBC}html pre.shiki code .syybb, html code.shiki .syybb{--shiki-default:#0E1116;--shiki-dark:#0E1116;--shiki-light:#0E1116}html pre.shiki code .sf6mN, html code.shiki .sf6mN{--shiki-default:#023B95;--shiki-dark:#023B95;--shiki-light:#023B95}",{"title":218,"searchDepth":231,"depth":231,"links":1614},[1615,1616,1617,1618,1619,1620,1621,1622],{"id":201,"depth":231,"text":202},{"id":401,"depth":231,"text":402},{"id":582,"depth":231,"text":583},{"id":609,"depth":231,"text":610},{"id":1024,"depth":231,"text":1025},{"id":1104,"depth":231,"text":1105},{"id":1419,"depth":231,"text":1420},{"id":1564,"depth":231,"text":1565},"Use scheduler.yield() and scheduler.postTask() to split long tasks and hold INP under 200ms.","md",{"slug":1626,"type":1627,"breadcrumb":1628,"datePublished":1635,"dateModified":1635},"optimizing-inp-with-scheduler-yield","cluster",[1629,1632,1633],{"name":1630,"url":1631},"Home","\u002F",{"name":22,"url":21},{"name":5,"url":1634},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F","2026-06-18","\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield",{"title":5,"description":1638},"Break up long tasks with scheduler.yield() and the Prioritized Task Scheduling API to keep INP under 200ms. Yield points, postTask priorities, and CI budgets.","core-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Findex","rz0d7jpT6BmycUsLrSF5GPv-ErVl-ogcq_3j6bFNaYA",[1642,1646],{"title":1643,"path":1644,"stem":1645,"children":-1},"Improving INP for Complex Single-Page Applications","\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Fimproving-inp-for-complex-single-page-applications","core-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002Fimproving-inp-for-complex-single-page-applications\u002Findex",{"title":1647,"path":1648,"stem":1649,"children":-1},"Breaking Up Long Tasks in React Event Handlers","\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fbreaking-up-long-tasks-in-react-event-handlers","core-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fbreaking-up-long-tasks-in-react-event-handlers\u002Findex",1782237170909]