[{"data":1,"prerenderedAt":1545},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-suspense-concurrent-guide":3},{"id":4,"title":5,"body":6,"description":1530,"difficulty":1531,"extension":1532,"framework":1533,"frameworkSlug":1534,"meta":1535,"navigation":70,"order":113,"path":1536,"qaPath":1537,"seo":1538,"stem":1539,"subtopic":1540,"topic":1541,"topicSlug":1542,"updated":1543,"__hash__":1544},"blog\u002Fblog\u002Freact-suspense-concurrent-guide.md","React Suspense and Concurrent Rendering — A Complete Guide",{"type":7,"value":8,"toc":1521},"minimark",[9,14,23,35,135,146,150,161,292,303,422,429,433,440,446,637,648,652,658,990,1006,1010,1023,1139,1153,1158,1220,1224,1238,1441,1452,1456,1505,1517],[10,11,13],"h2",{"id":12},"what-concurrent-rendering-actually-means","What concurrent rendering actually means",[15,16,17,18,22],"p",{},"In React 17 and below every render was ",[19,20,21],"strong",{},"synchronous and blocking",". Once React started computing a new tree it ran to completion — nothing else on the main thread could run until it finished. A slow render meant a frozen UI.",[15,24,25,26,29,30,34],{},"React 18's ",[19,27,28],{},"concurrent mode"," changes this by making rendering ",[31,32,33],"em",{},"interruptible",". React works in small time slices and regularly yields control back to the browser. If a higher-priority update arrives mid-render — say a keypress while a large list is filtering — React can pause the low-priority work, handle the urgent event, and then restart the deferred render from scratch.",[36,37,42],"pre",{"className":38,"code":39,"language":40,"meta":41,"style":41},"language-jsx shiki shiki-themes github-light github-dark","import { createRoot } from 'react-dom\u002Fclient'\n\n\u002F\u002F One-line opt-in to concurrent mode\nconst root = createRoot(document.getElementById('root'))\nroot.render(\u003CApp \u002F>)\n\u002F\u002F Legacy ReactDOM.render stays in blocking mode — no concurrent features\n","jsx","",[43,44,45,65,72,79,111,129],"code",{"__ignoreMap":41},[46,47,50,54,58,61],"span",{"class":48,"line":49},"line",1,[46,51,53],{"class":52},"szBVR","import",[46,55,57],{"class":56},"sVt8B"," { createRoot } ",[46,59,60],{"class":52},"from",[46,62,64],{"class":63},"sZZnC"," 'react-dom\u002Fclient'\n",[46,66,68],{"class":48,"line":67},2,[46,69,71],{"emptyLinePlaceholder":70},true,"\n",[46,73,75],{"class":48,"line":74},3,[46,76,78],{"class":77},"sJ8bj","\u002F\u002F One-line opt-in to concurrent mode\n",[46,80,82,85,89,92,96,99,102,105,108],{"class":48,"line":81},4,[46,83,84],{"class":52},"const",[46,86,88],{"class":87},"sj4cs"," root",[46,90,91],{"class":52}," =",[46,93,95],{"class":94},"sScJk"," createRoot",[46,97,98],{"class":56},"(document.",[46,100,101],{"class":94},"getElementById",[46,103,104],{"class":56},"(",[46,106,107],{"class":63},"'root'",[46,109,110],{"class":56},"))\n",[46,112,114,117,120,123,126],{"class":48,"line":113},5,[46,115,116],{"class":56},"root.",[46,118,119],{"class":94},"render",[46,121,122],{"class":56},"(\u003C",[46,124,125],{"class":87},"App",[46,127,128],{"class":56}," \u002F>)\n",[46,130,132],{"class":48,"line":131},6,[46,133,134],{"class":77},"\u002F\u002F Legacy ReactDOM.render stays in blocking mode — no concurrent features\n",[15,136,137,138,141,142,145],{},"The important consequence for developers: ",[19,139,140],{},"render functions must be pure",". React may call your component function multiple times for the same update during concurrent work. Side effects still belong in ",[43,143,144],{},"useEffect",", which runs exactly once per commit regardless of how many times React speculatively rendered.",[10,147,149],{"id":148},"suspense-boundaries","Suspense boundaries",[15,151,152,153,156,157,160],{},"A ",[43,154,155],{},"\u003CSuspense>"," boundary is a declarative loading fence. When any component inside it is not ready to render — because a code chunk is still downloading, or a data fetch is in-flight — React replaces the boundary's children with its ",[43,158,159],{},"fallback"," until every suspended component is ready.",[36,162,164],{"className":38,"code":163,"language":40,"meta":41,"style":41},"import { Suspense, lazy } from 'react'\n\nconst HeavyChart = lazy(() => import('.\u002FHeavyChart'))\n\nfunction Dashboard() {\n  return (\n    \u003CSuspense fallback={\u003CChartSkeleton \u002F>}>\n      \u003CHeavyChart \u002F>\n    \u003C\u002FSuspense>\n  )\n}\n",[43,165,166,178,182,210,214,225,233,257,269,280,286],{"__ignoreMap":41},[46,167,168,170,173,175],{"class":48,"line":49},[46,169,53],{"class":52},[46,171,172],{"class":56}," { Suspense, lazy } ",[46,174,60],{"class":52},[46,176,177],{"class":63}," 'react'\n",[46,179,180],{"class":48,"line":67},[46,181,71],{"emptyLinePlaceholder":70},[46,183,184,186,189,191,194,197,200,203,205,208],{"class":48,"line":74},[46,185,84],{"class":52},[46,187,188],{"class":87}," HeavyChart",[46,190,91],{"class":52},[46,192,193],{"class":94}," lazy",[46,195,196],{"class":56},"(() ",[46,198,199],{"class":52},"=>",[46,201,202],{"class":52}," import",[46,204,104],{"class":56},[46,206,207],{"class":63},"'.\u002FHeavyChart'",[46,209,110],{"class":56},[46,211,212],{"class":48,"line":81},[46,213,71],{"emptyLinePlaceholder":70},[46,215,216,219,222],{"class":48,"line":113},[46,217,218],{"class":52},"function",[46,220,221],{"class":94}," Dashboard",[46,223,224],{"class":56},"() {\n",[46,226,227,230],{"class":48,"line":131},[46,228,229],{"class":52},"  return",[46,231,232],{"class":56}," (\n",[46,234,236,239,242,245,248,251,254],{"class":48,"line":235},7,[46,237,238],{"class":56},"    \u003C",[46,240,241],{"class":87},"Suspense",[46,243,244],{"class":94}," fallback",[46,246,247],{"class":52},"=",[46,249,250],{"class":56},"{\u003C",[46,252,253],{"class":87},"ChartSkeleton",[46,255,256],{"class":56}," \u002F>}>\n",[46,258,260,263,266],{"class":48,"line":259},8,[46,261,262],{"class":56},"      \u003C",[46,264,265],{"class":87},"HeavyChart",[46,267,268],{"class":56}," \u002F>\n",[46,270,272,275,277],{"class":48,"line":271},9,[46,273,274],{"class":56},"    \u003C\u002F",[46,276,241],{"class":87},[46,278,279],{"class":56},">\n",[46,281,283],{"class":48,"line":282},10,[46,284,285],{"class":56},"  )\n",[46,287,289],{"class":48,"line":288},11,[46,290,291],{"class":56},"}\n",[15,293,294,295,298,299,302],{},"Nesting boundaries gives independent loading states to different regions. The ",[43,296,297],{},"\u003CSidebar>"," and ",[43,300,301],{},"\u003CFeed>"," below each show their own skeleton without blocking each other, and without blocking the header, which renders immediately.",[36,304,306],{"className":38,"code":305,"language":40,"meta":41,"style":41},"\u003C>\n  \u003CHeader \u002F>                                  {\u002F* renders right away *\u002F}\n\n  \u003CSuspense fallback={\u003CSidebarSkeleton \u002F>}>\n    \u003CSidebar \u002F>                               {\u002F* resolves on its own timeline *\u002F}\n  \u003C\u002FSuspense>\n\n  \u003CSuspense fallback={\u003CFeedSkeleton \u002F>}>\n    \u003CFeed \u002F>                                  {\u002F* resolves independently *\u002F}\n  \u003C\u002FSuspense>\n\u003C\u002F>\n",[43,307,308,313,329,333,350,365,374,378,395,409,417],{"__ignoreMap":41},[46,309,310],{"class":48,"line":49},[46,311,312],{"class":56},"\u003C>\n",[46,314,315,318,321,324,327],{"class":48,"line":67},[46,316,317],{"class":56},"  \u003C",[46,319,320],{"class":87},"Header",[46,322,323],{"class":56}," \u002F>                                  {",[46,325,326],{"class":77},"\u002F* renders right away *\u002F",[46,328,291],{"class":56},[46,330,331],{"class":48,"line":74},[46,332,71],{"emptyLinePlaceholder":70},[46,334,335,337,339,341,343,345,348],{"class":48,"line":81},[46,336,317],{"class":56},[46,338,241],{"class":87},[46,340,244],{"class":94},[46,342,247],{"class":52},[46,344,250],{"class":56},[46,346,347],{"class":87},"SidebarSkeleton",[46,349,256],{"class":56},[46,351,352,354,357,360,363],{"class":48,"line":113},[46,353,238],{"class":56},[46,355,356],{"class":87},"Sidebar",[46,358,359],{"class":56}," \u002F>                               {",[46,361,362],{"class":77},"\u002F* resolves on its own timeline *\u002F",[46,364,291],{"class":56},[46,366,367,370,372],{"class":48,"line":131},[46,368,369],{"class":56},"  \u003C\u002F",[46,371,241],{"class":87},[46,373,279],{"class":56},[46,375,376],{"class":48,"line":235},[46,377,71],{"emptyLinePlaceholder":70},[46,379,380,382,384,386,388,390,393],{"class":48,"line":259},[46,381,317],{"class":56},[46,383,241],{"class":87},[46,385,244],{"class":94},[46,387,247],{"class":52},[46,389,250],{"class":56},[46,391,392],{"class":87},"FeedSkeleton",[46,394,256],{"class":56},[46,396,397,399,402,404,407],{"class":48,"line":271},[46,398,238],{"class":56},[46,400,401],{"class":87},"Feed",[46,403,323],{"class":56},[46,405,406],{"class":77},"\u002F* resolves independently *\u002F",[46,408,291],{"class":56},[46,410,411,413,415],{"class":48,"line":282},[46,412,369],{"class":56},[46,414,241],{"class":87},[46,416,279],{"class":56},[46,418,419],{"class":48,"line":288},[46,420,421],{"class":56},"\u003C\u002F>\n",[15,423,424,425,428],{},"The golden rule: one Suspense boundary per ",[31,426,427],{},"independently loading region",", not one per component.",[10,430,432],{"id":431},"suspense-for-data-fetching-and-reactuse","Suspense for data fetching and React.use()",[15,434,435,436,439],{},"React 18 extended Suspense beyond lazy-loaded code to cover ",[31,437,438],{},"data"," — any Promise a component declares it depends on. The mechanism is the same: the component throws a Promise, React catches it, shows the fallback, and re-renders when the Promise resolves.",[15,441,442,445],{},[43,443,444],{},"React.use()"," (stable in React 19, experimental in React 18.3) is the standard way to trigger this:",[36,447,449],{"className":38,"code":448,"language":40,"meta":41,"style":41},"import { use, Suspense } from 'react'\n\n\u002F\u002F Kick off the fetch BEFORE rendering — \"render-as-you-fetch\"\nconst userPromise = fetch('\u002Fapi\u002Fme').then(r => r.json())\n\nfunction UserCard() {\n  const user = use(userPromise)   \u002F\u002F suspends if pending; throws if rejected\n  return \u003Ch2>Hello, {user.name}\u003C\u002Fh2>\n}\n\nfunction App() {\n  return (\n    \u003CSuspense fallback={\u003Cp>Loading profile…\u003C\u002Fp>}>\n      \u003CUserCard \u002F>\n    \u003C\u002FSuspense>\n  )\n}\n",[43,450,451,462,466,471,512,516,525,544,561,565,569,578,585,608,618,627,632],{"__ignoreMap":41},[46,452,453,455,458,460],{"class":48,"line":49},[46,454,53],{"class":52},[46,456,457],{"class":56}," { use, Suspense } ",[46,459,60],{"class":52},[46,461,177],{"class":63},[46,463,464],{"class":48,"line":67},[46,465,71],{"emptyLinePlaceholder":70},[46,467,468],{"class":48,"line":74},[46,469,470],{"class":77},"\u002F\u002F Kick off the fetch BEFORE rendering — \"render-as-you-fetch\"\n",[46,472,473,475,478,480,483,485,488,491,494,496,500,503,506,509],{"class":48,"line":81},[46,474,84],{"class":52},[46,476,477],{"class":87}," userPromise",[46,479,91],{"class":52},[46,481,482],{"class":94}," fetch",[46,484,104],{"class":56},[46,486,487],{"class":63},"'\u002Fapi\u002Fme'",[46,489,490],{"class":56},").",[46,492,493],{"class":94},"then",[46,495,104],{"class":56},[46,497,499],{"class":498},"s4XuR","r",[46,501,502],{"class":52}," =>",[46,504,505],{"class":56}," r.",[46,507,508],{"class":94},"json",[46,510,511],{"class":56},"())\n",[46,513,514],{"class":48,"line":113},[46,515,71],{"emptyLinePlaceholder":70},[46,517,518,520,523],{"class":48,"line":131},[46,519,218],{"class":52},[46,521,522],{"class":94}," UserCard",[46,524,224],{"class":56},[46,526,527,530,533,535,538,541],{"class":48,"line":235},[46,528,529],{"class":52},"  const",[46,531,532],{"class":87}," user",[46,534,91],{"class":52},[46,536,537],{"class":94}," use",[46,539,540],{"class":56},"(userPromise)   ",[46,542,543],{"class":77},"\u002F\u002F suspends if pending; throws if rejected\n",[46,545,546,548,551,554,557,559],{"class":48,"line":259},[46,547,229],{"class":52},[46,549,550],{"class":56}," \u003C",[46,552,10],{"class":553},"s9eBZ",[46,555,556],{"class":56},">Hello, {user.name}\u003C\u002F",[46,558,10],{"class":553},[46,560,279],{"class":56},[46,562,563],{"class":48,"line":271},[46,564,291],{"class":56},[46,566,567],{"class":48,"line":282},[46,568,71],{"emptyLinePlaceholder":70},[46,570,571,573,576],{"class":48,"line":288},[46,572,218],{"class":52},[46,574,575],{"class":94}," App",[46,577,224],{"class":56},[46,579,581,583],{"class":48,"line":580},12,[46,582,229],{"class":52},[46,584,232],{"class":56},[46,586,588,590,592,594,596,598,600,603,605],{"class":48,"line":587},13,[46,589,238],{"class":56},[46,591,241],{"class":87},[46,593,244],{"class":94},[46,595,247],{"class":52},[46,597,250],{"class":56},[46,599,15],{"class":553},[46,601,602],{"class":56},">Loading profile…\u003C\u002F",[46,604,15],{"class":553},[46,606,607],{"class":56},">}>\n",[46,609,611,613,616],{"class":48,"line":610},14,[46,612,262],{"class":56},[46,614,615],{"class":87},"UserCard",[46,617,268],{"class":56},[46,619,621,623,625],{"class":48,"line":620},15,[46,622,274],{"class":56},[46,624,241],{"class":87},[46,626,279],{"class":56},[46,628,630],{"class":48,"line":629},16,[46,631,285],{"class":56},[46,633,635],{"class":48,"line":634},17,[46,636,291],{"class":56},[15,638,639,640,643,644,647],{},"The critical pattern above is ",[19,641,642],{},"render-as-you-fetch",": the Promise is created ",[31,645,646],{},"outside"," the component, before React renders it. This eliminates the mount-then-fetch waterfall where a component has to render once just to know what to fetch.",[10,649,651],{"id":650},"usetransition-for-non-urgent-updates","useTransition for non-urgent updates",[15,653,654,657],{},[43,655,656],{},"useTransition"," lets you classify a state update as low priority so React keeps the current UI interactive while computing the new one.",[36,659,661],{"className":38,"code":660,"language":40,"meta":41,"style":41},"import { useState, useTransition } from 'react'\n\nfunction SearchPage({ items }) {\n  const [query, setQuery]     = useState('')\n  const [results, setResults] = useState(items)\n  const [isPending, startTransition] = useTransition()\n\n  function handleChange(e) {\n    setQuery(e.target.value)                          \u002F\u002F urgent — input stays snappy\n    startTransition(() => {\n      setResults(items.filter(i =>\n        i.name.toLowerCase().includes(e.target.value)\n      ))                                              \u002F\u002F non-urgent — can be deferred\n    })\n  }\n\n  return (\n    \u003C>\n      \u003Cinput value={query} onChange={handleChange} \u002F>\n      {isPending && \u003Cspan>Updating…\u003C\u002Fspan>}\n      \u003Cul>{results.map(i => \u003Cli key={i.id}>{i.name}\u003C\u002Fli>)}\u003C\u002Ful>\n    \u003C\u002F>\n  )\n}\n",[43,662,663,674,678,694,726,750,774,778,794,805,817,836,853,861,866,871,875,881,887,911,932,974,980,985],{"__ignoreMap":41},[46,664,665,667,670,672],{"class":48,"line":49},[46,666,53],{"class":52},[46,668,669],{"class":56}," { useState, useTransition } ",[46,671,60],{"class":52},[46,673,177],{"class":63},[46,675,676],{"class":48,"line":67},[46,677,71],{"emptyLinePlaceholder":70},[46,679,680,682,685,688,691],{"class":48,"line":74},[46,681,218],{"class":52},[46,683,684],{"class":94}," SearchPage",[46,686,687],{"class":56},"({ ",[46,689,690],{"class":498},"items",[46,692,693],{"class":56}," }) {\n",[46,695,696,698,701,704,707,710,713,715,718,720,723],{"class":48,"line":81},[46,697,529],{"class":52},[46,699,700],{"class":56}," [",[46,702,703],{"class":87},"query",[46,705,706],{"class":56},", ",[46,708,709],{"class":87},"setQuery",[46,711,712],{"class":56},"]     ",[46,714,247],{"class":52},[46,716,717],{"class":94}," useState",[46,719,104],{"class":56},[46,721,722],{"class":63},"''",[46,724,725],{"class":56},")\n",[46,727,728,730,732,735,737,740,743,745,747],{"class":48,"line":113},[46,729,529],{"class":52},[46,731,700],{"class":56},[46,733,734],{"class":87},"results",[46,736,706],{"class":56},[46,738,739],{"class":87},"setResults",[46,741,742],{"class":56},"] ",[46,744,247],{"class":52},[46,746,717],{"class":94},[46,748,749],{"class":56},"(items)\n",[46,751,752,754,756,759,761,764,766,768,771],{"class":48,"line":131},[46,753,529],{"class":52},[46,755,700],{"class":56},[46,757,758],{"class":87},"isPending",[46,760,706],{"class":56},[46,762,763],{"class":87},"startTransition",[46,765,742],{"class":56},[46,767,247],{"class":52},[46,769,770],{"class":94}," useTransition",[46,772,773],{"class":56},"()\n",[46,775,776],{"class":48,"line":235},[46,777,71],{"emptyLinePlaceholder":70},[46,779,780,783,786,788,791],{"class":48,"line":259},[46,781,782],{"class":52},"  function",[46,784,785],{"class":94}," handleChange",[46,787,104],{"class":56},[46,789,790],{"class":498},"e",[46,792,793],{"class":56},") {\n",[46,795,796,799,802],{"class":48,"line":271},[46,797,798],{"class":94},"    setQuery",[46,800,801],{"class":56},"(e.target.value)                          ",[46,803,804],{"class":77},"\u002F\u002F urgent — input stays snappy\n",[46,806,807,810,812,814],{"class":48,"line":282},[46,808,809],{"class":94},"    startTransition",[46,811,196],{"class":56},[46,813,199],{"class":52},[46,815,816],{"class":56}," {\n",[46,818,819,822,825,828,830,833],{"class":48,"line":288},[46,820,821],{"class":94},"      setResults",[46,823,824],{"class":56},"(items.",[46,826,827],{"class":94},"filter",[46,829,104],{"class":56},[46,831,832],{"class":498},"i",[46,834,835],{"class":52}," =>\n",[46,837,838,841,844,847,850],{"class":48,"line":580},[46,839,840],{"class":56},"        i.name.",[46,842,843],{"class":94},"toLowerCase",[46,845,846],{"class":56},"().",[46,848,849],{"class":94},"includes",[46,851,852],{"class":56},"(e.target.value)\n",[46,854,855,858],{"class":48,"line":587},[46,856,857],{"class":56},"      ))                                              ",[46,859,860],{"class":77},"\u002F\u002F non-urgent — can be deferred\n",[46,862,863],{"class":48,"line":610},[46,864,865],{"class":56},"    })\n",[46,867,868],{"class":48,"line":620},[46,869,870],{"class":56},"  }\n",[46,872,873],{"class":48,"line":629},[46,874,71],{"emptyLinePlaceholder":70},[46,876,877,879],{"class":48,"line":634},[46,878,229],{"class":52},[46,880,232],{"class":56},[46,882,884],{"class":48,"line":883},18,[46,885,886],{"class":56},"    \u003C>\n",[46,888,890,892,895,898,900,903,906,908],{"class":48,"line":889},19,[46,891,262],{"class":56},[46,893,894],{"class":553},"input",[46,896,897],{"class":94}," value",[46,899,247],{"class":52},[46,901,902],{"class":56},"{query} ",[46,904,905],{"class":94},"onChange",[46,907,247],{"class":52},[46,909,910],{"class":56},"{handleChange} \u002F>\n",[46,912,914,917,920,922,924,927,929],{"class":48,"line":913},20,[46,915,916],{"class":56},"      {isPending ",[46,918,919],{"class":52},"&&",[46,921,550],{"class":56},[46,923,46],{"class":553},[46,925,926],{"class":56},">Updating…\u003C\u002F",[46,928,46],{"class":553},[46,930,931],{"class":56},">}\n",[46,933,935,937,940,943,946,948,950,952,954,957,960,962,965,967,970,972],{"class":48,"line":934},21,[46,936,262],{"class":56},[46,938,939],{"class":553},"ul",[46,941,942],{"class":56},">{results.",[46,944,945],{"class":94},"map",[46,947,104],{"class":56},[46,949,832],{"class":498},[46,951,502],{"class":52},[46,953,550],{"class":56},[46,955,956],{"class":553},"li",[46,958,959],{"class":94}," key",[46,961,247],{"class":52},[46,963,964],{"class":56},"{i.id}>{i.name}\u003C\u002F",[46,966,956],{"class":553},[46,968,969],{"class":56},">)}\u003C\u002F",[46,971,939],{"class":553},[46,973,279],{"class":56},[46,975,977],{"class":48,"line":976},22,[46,978,979],{"class":56},"    \u003C\u002F>\n",[46,981,983],{"class":48,"line":982},23,[46,984,285],{"class":56},[46,986,988],{"class":48,"line":987},24,[46,989,291],{"class":56},[15,991,992,994,995,998,999,1001,1002,1005],{},[43,993,758],{}," is ",[43,996,997],{},"true"," while the deferred render is in progress, making it easy to show a subtle indicator. Because ",[43,1000,763],{}," wraps the setter, you control the deferral at the ",[31,1003,1004],{},"source"," of the update.",[10,1007,1009],{"id":1008},"usedeferredvalue-for-derived-state","useDeferredValue for derived state",[15,1011,1012,1015,1016,1018,1019,1022],{},[43,1013,1014],{},"useDeferredValue"," is the complement to ",[43,1017,656],{},". Instead of wrapping the setter, it wraps a ",[31,1020,1021],{},"received value"," — useful when the state is owned by a parent you cannot modify.",[36,1024,1026],{"className":38,"code":1025,"language":40,"meta":41,"style":41},"import { useDeferredValue } from 'react'\n\nfunction ExpensiveList({ query }) {\n  \u002F\u002F query comes from a parent — we can't add startTransition there\n  const deferredQuery = useDeferredValue(query)\n\n  const filtered = heavyFilter(deferredQuery)   \u002F\u002F runs with the stale value first\n  return \u003Cul>{filtered.map(i => \u003Cli key={i.id}>{i.name}\u003C\u002Fli>)}\u003C\u002Ful>\n}\n",[43,1027,1028,1039,1043,1056,1061,1076,1080,1098,1135],{"__ignoreMap":41},[46,1029,1030,1032,1035,1037],{"class":48,"line":49},[46,1031,53],{"class":52},[46,1033,1034],{"class":56}," { useDeferredValue } ",[46,1036,60],{"class":52},[46,1038,177],{"class":63},[46,1040,1041],{"class":48,"line":67},[46,1042,71],{"emptyLinePlaceholder":70},[46,1044,1045,1047,1050,1052,1054],{"class":48,"line":74},[46,1046,218],{"class":52},[46,1048,1049],{"class":94}," ExpensiveList",[46,1051,687],{"class":56},[46,1053,703],{"class":498},[46,1055,693],{"class":56},[46,1057,1058],{"class":48,"line":81},[46,1059,1060],{"class":77},"  \u002F\u002F query comes from a parent — we can't add startTransition there\n",[46,1062,1063,1065,1068,1070,1073],{"class":48,"line":113},[46,1064,529],{"class":52},[46,1066,1067],{"class":87}," deferredQuery",[46,1069,91],{"class":52},[46,1071,1072],{"class":94}," useDeferredValue",[46,1074,1075],{"class":56},"(query)\n",[46,1077,1078],{"class":48,"line":131},[46,1079,71],{"emptyLinePlaceholder":70},[46,1081,1082,1084,1087,1089,1092,1095],{"class":48,"line":235},[46,1083,529],{"class":52},[46,1085,1086],{"class":87}," filtered",[46,1088,91],{"class":52},[46,1090,1091],{"class":94}," heavyFilter",[46,1093,1094],{"class":56},"(deferredQuery)   ",[46,1096,1097],{"class":77},"\u002F\u002F runs with the stale value first\n",[46,1099,1100,1102,1104,1106,1109,1111,1113,1115,1117,1119,1121,1123,1125,1127,1129,1131,1133],{"class":48,"line":259},[46,1101,229],{"class":52},[46,1103,550],{"class":56},[46,1105,939],{"class":553},[46,1107,1108],{"class":56},">{filtered.",[46,1110,945],{"class":94},[46,1112,104],{"class":56},[46,1114,832],{"class":498},[46,1116,502],{"class":52},[46,1118,550],{"class":56},[46,1120,956],{"class":553},[46,1122,959],{"class":94},[46,1124,247],{"class":52},[46,1126,964],{"class":56},[46,1128,956],{"class":553},[46,1130,969],{"class":56},[46,1132,939],{"class":553},[46,1134,279],{"class":56},[46,1136,1137],{"class":48,"line":271},[46,1138,291],{"class":56},[15,1140,1141,1142,1144,1145,1148,1149,1152],{},"React renders the parent immediately with the latest ",[43,1143,703],{}," (for the input), then re-renders ",[43,1146,1147],{},"ExpensiveList"," in the background with ",[43,1150,1151],{},"deferredQuery"," catching up. The user sees a snappy input and a briefly stale list — which is almost always preferable to a janky UI.",[15,1154,1155],{},[19,1156,1157],{},"When to choose which:",[1159,1160,1161,1174],"table",{},[1162,1163,1164],"thead",{},[1165,1166,1167,1171],"tr",{},[1168,1169,1170],"th",{},"Scenario",[1168,1172,1173],{},"API",[1175,1176,1177,1187,1196,1209],"tbody",{},[1165,1178,1179,1183],{},[1180,1181,1182],"td",{},"You own the state setter",[1180,1184,1185],{},[43,1186,656],{},[1165,1188,1189,1192],{},[1180,1190,1191],{},"Value arrives as a prop\u002Fcontext",[1180,1193,1194],{},[43,1195,1014],{},[1165,1197,1198,1201],{},[1180,1199,1200],{},"Need to show a pending indicator",[1180,1202,1203,1205,1206,1208],{},[43,1204,656],{}," (has ",[43,1207,758],{},")",[1165,1210,1211,1214],{},[1180,1212,1213],{},"Deferring an expensive child render",[1180,1215,1216,1217,1219],{},"Either; ",[43,1218,1014],{}," is simpler",[10,1221,1223],{"id":1222},"streaming-ssr-with-suspense-in-nextjs","Streaming SSR with Suspense in Next.js",[15,1225,1226,1227,1229,1230,1233,1234,1237],{},"Next.js App Router runs React Server Components on the server and uses HTTP streaming to send HTML to the browser in chunks. Each ",[43,1228,155],{}," boundary is a ",[19,1231,1232],{},"streaming flush point",": the shell HTML (layout, headings, skeleton fallbacks) arrives instantly, and as each async Server Component resolves its ",[43,1235,1236],{},"await",", Next.js streams the finished HTML chunk and a hydration script that swaps the skeleton out.",[36,1239,1241],{"className":38,"code":1240,"language":40,"meta":41,"style":41},"\u002F\u002F app\u002Fdashboard\u002Fpage.tsx\nimport { Suspense } from 'react'\nimport { UserStats }    from '.\u002FUserStats'     \u002F\u002F awaits a DB query\nimport { RecentOrders } from '.\u002FRecentOrders'  \u002F\u002F awaits a different DB query\n\nexport default function Dashboard() {\n  return (\n    \u003Cmain>\n      \u003Ch1>Dashboard\u003C\u002Fh1>\n\n      {\u002F* Shell arrives immediately; each chunk streams as its query resolves *\u002F}\n      \u003CSuspense fallback={\u003CStatsSkeleton \u002F>}>\n        \u003CUserStats \u002F>\n      \u003C\u002FSuspense>\n\n      \u003CSuspense fallback={\u003COrdersSkeleton \u002F>}>\n        \u003CRecentOrders \u002F>\n      \u003C\u002FSuspense>\n    \u003C\u002Fmain>\n  )\n}\n",[43,1242,1243,1248,1259,1274,1289,1293,1308,1314,1323,1337,1341,1351,1368,1378,1387,1391,1408,1417,1425,1433,1437],{"__ignoreMap":41},[46,1244,1245],{"class":48,"line":49},[46,1246,1247],{"class":77},"\u002F\u002F app\u002Fdashboard\u002Fpage.tsx\n",[46,1249,1250,1252,1255,1257],{"class":48,"line":67},[46,1251,53],{"class":52},[46,1253,1254],{"class":56}," { Suspense } ",[46,1256,60],{"class":52},[46,1258,177],{"class":63},[46,1260,1261,1263,1266,1268,1271],{"class":48,"line":74},[46,1262,53],{"class":52},[46,1264,1265],{"class":56}," { UserStats }    ",[46,1267,60],{"class":52},[46,1269,1270],{"class":63}," '.\u002FUserStats'",[46,1272,1273],{"class":77},"     \u002F\u002F awaits a DB query\n",[46,1275,1276,1278,1281,1283,1286],{"class":48,"line":81},[46,1277,53],{"class":52},[46,1279,1280],{"class":56}," { RecentOrders } ",[46,1282,60],{"class":52},[46,1284,1285],{"class":63}," '.\u002FRecentOrders'",[46,1287,1288],{"class":77},"  \u002F\u002F awaits a different DB query\n",[46,1290,1291],{"class":48,"line":113},[46,1292,71],{"emptyLinePlaceholder":70},[46,1294,1295,1298,1301,1304,1306],{"class":48,"line":131},[46,1296,1297],{"class":52},"export",[46,1299,1300],{"class":52}," default",[46,1302,1303],{"class":52}," function",[46,1305,221],{"class":94},[46,1307,224],{"class":56},[46,1309,1310,1312],{"class":48,"line":235},[46,1311,229],{"class":52},[46,1313,232],{"class":56},[46,1315,1316,1318,1321],{"class":48,"line":259},[46,1317,238],{"class":56},[46,1319,1320],{"class":553},"main",[46,1322,279],{"class":56},[46,1324,1325,1327,1330,1333,1335],{"class":48,"line":271},[46,1326,262],{"class":56},[46,1328,1329],{"class":553},"h1",[46,1331,1332],{"class":56},">Dashboard\u003C\u002F",[46,1334,1329],{"class":553},[46,1336,279],{"class":56},[46,1338,1339],{"class":48,"line":282},[46,1340,71],{"emptyLinePlaceholder":70},[46,1342,1343,1346,1349],{"class":48,"line":288},[46,1344,1345],{"class":56},"      {",[46,1347,1348],{"class":77},"\u002F* Shell arrives immediately; each chunk streams as its query resolves *\u002F",[46,1350,291],{"class":56},[46,1352,1353,1355,1357,1359,1361,1363,1366],{"class":48,"line":580},[46,1354,262],{"class":56},[46,1356,241],{"class":87},[46,1358,244],{"class":94},[46,1360,247],{"class":52},[46,1362,250],{"class":56},[46,1364,1365],{"class":87},"StatsSkeleton",[46,1367,256],{"class":56},[46,1369,1370,1373,1376],{"class":48,"line":587},[46,1371,1372],{"class":56},"        \u003C",[46,1374,1375],{"class":87},"UserStats",[46,1377,268],{"class":56},[46,1379,1380,1383,1385],{"class":48,"line":610},[46,1381,1382],{"class":56},"      \u003C\u002F",[46,1384,241],{"class":87},[46,1386,279],{"class":56},[46,1388,1389],{"class":48,"line":620},[46,1390,71],{"emptyLinePlaceholder":70},[46,1392,1393,1395,1397,1399,1401,1403,1406],{"class":48,"line":629},[46,1394,262],{"class":56},[46,1396,241],{"class":87},[46,1398,244],{"class":94},[46,1400,247],{"class":52},[46,1402,250],{"class":56},[46,1404,1405],{"class":87},"OrdersSkeleton",[46,1407,256],{"class":56},[46,1409,1410,1412,1415],{"class":48,"line":634},[46,1411,1372],{"class":56},[46,1413,1414],{"class":87},"RecentOrders",[46,1416,268],{"class":56},[46,1418,1419,1421,1423],{"class":48,"line":883},[46,1420,1382],{"class":56},[46,1422,241],{"class":87},[46,1424,279],{"class":56},[46,1426,1427,1429,1431],{"class":48,"line":889},[46,1428,274],{"class":56},[46,1430,1320],{"class":553},[46,1432,279],{"class":56},[46,1434,1435],{"class":48,"line":913},[46,1436,285],{"class":56},[46,1438,1439],{"class":48,"line":934},[46,1440,291],{"class":56},[15,1442,1443,1444,1447,1448,1451],{},"Without Suspense boundaries the server would wait for ",[31,1445,1446],{},"all"," data before sending ",[31,1449,1450],{},"any"," HTML, making Time to First Byte as slow as your slowest query. With boundaries the page is progressively useful from the first byte.",[10,1453,1455],{"id":1454},"choosing-the-right-api","Choosing the right API",[939,1457,1458,1469,1479,1486,1493],{},[956,1459,1460,1468],{},[19,1461,1462,1465,1466],{},[43,1463,1464],{},"React.lazy"," + ",[43,1467,155],{}," — always the right call for route-level code splitting.",[956,1470,1471,1478],{},[19,1472,1473,1465,1476],{},[43,1474,1475],{},"React.use(promise)",[43,1477,155],{}," — data fetching in React 19 \u002F Next.js App Router.",[956,1480,1481,1485],{},[19,1482,1483],{},[43,1484,656],{}," — any expensive client-side state update you own (navigation, filtering).",[956,1487,1488,1492],{},[19,1489,1490],{},[43,1491,1014],{}," — expensive render triggered by a prop or context you do not own.",[956,1494,1495,1500,1501,1504],{},[19,1496,1497],{},[43,1498,1499],{},"createRoot"," — the single switch that unlocks all of the above; migrate from ",[43,1502,1503],{},"ReactDOM.render"," as a first step.",[15,1506,1507,1508,1510,1511,1513,1514,1516],{},"Concurrent features are additive. You can adopt them incrementally: upgrade to ",[43,1509,1499],{},", wrap a slow route in ",[43,1512,155],{},", add ",[43,1515,656],{}," to the one search box that feels sluggish. Each step is independent and reversible.",[1518,1519,1520],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":41,"searchDepth":67,"depth":67,"links":1522},[1523,1524,1525,1526,1527,1528,1529],{"id":12,"depth":67,"text":13},{"id":148,"depth":67,"text":149},{"id":431,"depth":67,"text":432},{"id":650,"depth":67,"text":651},{"id":1008,"depth":67,"text":1009},{"id":1222,"depth":67,"text":1223},{"id":1454,"depth":67,"text":1455},"React Suspense and concurrent rendering interview questions — Suspense for data fetching, useTransition, useDeferredValue, startTransition, concurrent features, and React 18 rendering model.","hard","md","React","react",{},"\u002Fblog\u002Freact-suspense-concurrent-guide","\u002Freact\u002Frendering-and-performance\u002Fsuspense-concurrent",{"title":5,"description":1530},"blog\u002Freact-suspense-concurrent-guide","Suspense and Concurrent Rendering","Rendering and Performance","rendering-and-performance","2026-06-24","iD8-EmBLtTYpWqiAT2Z2krjru7Kl6Vf6dxL0IWgh9mo",1782244083331]