[{"data":1,"prerenderedAt":1482},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-code-splitting-lazy-guide":3},{"id":4,"title":5,"body":6,"description":1467,"difficulty":1468,"extension":1469,"framework":1470,"frameworkSlug":1471,"meta":1472,"navigation":87,"order":98,"path":1473,"qaPath":1474,"seo":1475,"stem":1476,"subtopic":1477,"topic":1478,"topicSlug":1479,"updated":1480,"__hash__":1481},"blog\u002Fblog\u002Freact-code-splitting-lazy-guide.md","React Code Splitting and Lazy Loading — A Complete Guide",{"type":7,"value":8,"toc":1457},"minimark",[9,14,18,34,38,53,239,257,261,269,280,428,435,439,442,735,742,746,760,1050,1057,1061,1071,1275,1286,1290,1293,1393,1408,1412,1453],[10,11,13],"h2",{"id":12},"the-problem-one-giant-bundle","The problem: one giant bundle",[15,16,17],"p",{},"When you build a React app without code splitting, every component, library, and\nutility ends up in a single JavaScript file. A user who lands on your home page\ndownloads the code for the settings panel, the admin dashboard, the PDF viewer —\neverything — before they see anything interactive.",[15,19,20,21,25,26,29,30,33],{},"The result is a high ",[22,23,24],"strong",{},"Time to Interactive (TTI)",", slow ",[22,27,28],{},"First Contentful\nPaint (FCP)",", and unnecessary parse\u002Fcompile work on the main thread. Code\nsplitting fixes this by breaking the bundle into smaller ",[22,31,32],{},"chunks"," that are\nfetched on demand, so the initial page only loads what it actually needs.",[10,35,37],{"id":36},"reactlazy-and-dynamic-import","React.lazy and dynamic import()",[15,39,40,41,45,46,49,50,52],{},"React's built-in answer to component-level splitting is ",[42,43,44],"code",{},"React.lazy"," paired with\nthe native ",[42,47,48],{},"import()"," function. The dynamic ",[42,51,48],{}," is a bundler hint —\nWebpack and Vite see it and automatically emit a separate chunk at build time.",[54,55,60],"pre",{"className":56,"code":57,"language":58,"meta":59,"style":59},"language-jsx shiki shiki-themes github-light github-dark","import { lazy, Suspense } from 'react'\n\n\u002F\u002F Static import — always in the initial bundle\n\u002F\u002F import Dashboard from '.\u002FDashboard'\n\n\u002F\u002F Lazy import — Dashboard becomes its own chunk, loaded on demand\nconst Dashboard = lazy(() => import('.\u002FDashboard'))\n\nfunction App() {\n  return (\n    \u003CSuspense fallback={\u003Cdiv>Loading dashboard…\u003C\u002Fdiv>}>\n      \u003CDashboard \u002F>\n    \u003C\u002FSuspense>\n  )\n}\n","jsx","",[42,61,62,82,89,96,102,107,113,148,153,165,174,204,216,227,233],{"__ignoreMap":59},[63,64,67,71,75,78],"span",{"class":65,"line":66},"line",1,[63,68,70],{"class":69},"szBVR","import",[63,72,74],{"class":73},"sVt8B"," { lazy, Suspense } ",[63,76,77],{"class":69},"from",[63,79,81],{"class":80},"sZZnC"," 'react'\n",[63,83,85],{"class":65,"line":84},2,[63,86,88],{"emptyLinePlaceholder":87},true,"\n",[63,90,92],{"class":65,"line":91},3,[63,93,95],{"class":94},"sJ8bj","\u002F\u002F Static import — always in the initial bundle\n",[63,97,99],{"class":65,"line":98},4,[63,100,101],{"class":94},"\u002F\u002F import Dashboard from '.\u002FDashboard'\n",[63,103,105],{"class":65,"line":104},5,[63,106,88],{"emptyLinePlaceholder":87},[63,108,110],{"class":65,"line":109},6,[63,111,112],{"class":94},"\u002F\u002F Lazy import — Dashboard becomes its own chunk, loaded on demand\n",[63,114,116,119,123,126,130,133,136,139,142,145],{"class":65,"line":115},7,[63,117,118],{"class":69},"const",[63,120,122],{"class":121},"sj4cs"," Dashboard",[63,124,125],{"class":69}," =",[63,127,129],{"class":128},"sScJk"," lazy",[63,131,132],{"class":73},"(() ",[63,134,135],{"class":69},"=>",[63,137,138],{"class":69}," import",[63,140,141],{"class":73},"(",[63,143,144],{"class":80},"'.\u002FDashboard'",[63,146,147],{"class":73},"))\n",[63,149,151],{"class":65,"line":150},8,[63,152,88],{"emptyLinePlaceholder":87},[63,154,156,159,162],{"class":65,"line":155},9,[63,157,158],{"class":69},"function",[63,160,161],{"class":128}," App",[63,163,164],{"class":73},"() {\n",[63,166,168,171],{"class":65,"line":167},10,[63,169,170],{"class":69},"  return",[63,172,173],{"class":73}," (\n",[63,175,177,180,183,186,189,192,196,199,201],{"class":65,"line":176},11,[63,178,179],{"class":73},"    \u003C",[63,181,182],{"class":121},"Suspense",[63,184,185],{"class":128}," fallback",[63,187,188],{"class":69},"=",[63,190,191],{"class":73},"{\u003C",[63,193,195],{"class":194},"s9eBZ","div",[63,197,198],{"class":73},">Loading dashboard…\u003C\u002F",[63,200,195],{"class":194},[63,202,203],{"class":73},">}>\n",[63,205,207,210,213],{"class":65,"line":206},12,[63,208,209],{"class":73},"      \u003C",[63,211,212],{"class":121},"Dashboard",[63,214,215],{"class":73}," \u002F>\n",[63,217,219,222,224],{"class":65,"line":218},13,[63,220,221],{"class":73},"    \u003C\u002F",[63,223,182],{"class":121},[63,225,226],{"class":73},">\n",[63,228,230],{"class":65,"line":229},14,[63,231,232],{"class":73},"  )\n",[63,234,236],{"class":65,"line":235},15,[63,237,238],{"class":73},"}\n",[15,240,241,242,245,246,248,249,252,253,256],{},"When React first tries to render ",[42,243,244],{},"\u003CDashboard \u002F>",", it fires the ",[42,247,48],{}," call.\nWhile the chunk is in flight, the nearest ",[42,250,251],{},"\u003CSuspense>"," boundary renders the\n",[42,254,255],{},"fallback",". Once the chunk resolves, React swaps in the real component. On\nsubsequent renders the module is cached — the fallback never shows again.",[10,258,260],{"id":259},"suspense-boundaries-placement-and-granularity","Suspense boundaries: placement and granularity",[15,262,263,265,266,268],{},[42,264,44],{}," requires a ",[42,267,251],{}," ancestor somewhere above it in the tree.\nWhere you place the boundary controls how much of the UI is replaced by the\nfallback during loading.",[15,270,271,272,275,276,279],{},"A ",[22,273,274],{},"single top-level boundary"," shows a full-page spinner — simple, but the\nwhole page blanks out while any lazy chunk is pending. ",[22,277,278],{},"Nested boundaries","\nlet each section load independently, showing skeletons that match the real\nlayout and keeping the rest of the page fully interactive.",[54,281,283],{"className":56,"code":282,"language":58,"meta":59,"style":59},"\u002F\u002F Top-level — entire page replaced by fallback\n\u003CSuspense fallback={\u003CPageSpinner \u002F>}>\n  \u003CSidebar \u002F>\n  \u003CMainContent \u002F>\n\u003C\u002FSuspense>\n\n\u002F\u002F Nested — each section has its own fallback\n\u003C>\n  \u003CSuspense fallback={\u003CSidebarSkeleton \u002F>}>\n    \u003CSidebar \u002F>\n  \u003C\u002FSuspense>\n\n  \u003CSuspense fallback={\u003CContentSkeleton \u002F>}>\n    \u003CMainContent \u002F>\n  \u003C\u002FSuspense>\n\u003C\u002F>\n",[42,284,285,290,309,319,328,337,341,346,351,368,376,385,389,406,414,422],{"__ignoreMap":59},[63,286,287],{"class":65,"line":66},[63,288,289],{"class":94},"\u002F\u002F Top-level — entire page replaced by fallback\n",[63,291,292,295,297,299,301,303,306],{"class":65,"line":84},[63,293,294],{"class":73},"\u003C",[63,296,182],{"class":121},[63,298,185],{"class":128},[63,300,188],{"class":69},[63,302,191],{"class":73},[63,304,305],{"class":121},"PageSpinner",[63,307,308],{"class":73}," \u002F>}>\n",[63,310,311,314,317],{"class":65,"line":91},[63,312,313],{"class":73},"  \u003C",[63,315,316],{"class":121},"Sidebar",[63,318,215],{"class":73},[63,320,321,323,326],{"class":65,"line":98},[63,322,313],{"class":73},[63,324,325],{"class":121},"MainContent",[63,327,215],{"class":73},[63,329,330,333,335],{"class":65,"line":104},[63,331,332],{"class":73},"\u003C\u002F",[63,334,182],{"class":121},[63,336,226],{"class":73},[63,338,339],{"class":65,"line":109},[63,340,88],{"emptyLinePlaceholder":87},[63,342,343],{"class":65,"line":115},[63,344,345],{"class":94},"\u002F\u002F Nested — each section has its own fallback\n",[63,347,348],{"class":65,"line":150},[63,349,350],{"class":73},"\u003C>\n",[63,352,353,355,357,359,361,363,366],{"class":65,"line":155},[63,354,313],{"class":73},[63,356,182],{"class":121},[63,358,185],{"class":128},[63,360,188],{"class":69},[63,362,191],{"class":73},[63,364,365],{"class":121},"SidebarSkeleton",[63,367,308],{"class":73},[63,369,370,372,374],{"class":65,"line":167},[63,371,179],{"class":73},[63,373,316],{"class":121},[63,375,215],{"class":73},[63,377,378,381,383],{"class":65,"line":176},[63,379,380],{"class":73},"  \u003C\u002F",[63,382,182],{"class":121},[63,384,226],{"class":73},[63,386,387],{"class":65,"line":206},[63,388,88],{"emptyLinePlaceholder":87},[63,390,391,393,395,397,399,401,404],{"class":65,"line":218},[63,392,313],{"class":73},[63,394,182],{"class":121},[63,396,185],{"class":128},[63,398,188],{"class":69},[63,400,191],{"class":73},[63,402,403],{"class":121},"ContentSkeleton",[63,405,308],{"class":73},[63,407,408,410,412],{"class":65,"line":229},[63,409,179],{"class":73},[63,411,325],{"class":121},[63,413,215],{"class":73},[63,415,416,418,420],{"class":65,"line":235},[63,417,380],{"class":73},[63,419,182],{"class":121},[63,421,226],{"class":73},[63,423,425],{"class":65,"line":424},16,[63,426,427],{"class":73},"\u003C\u002F>\n",[15,429,430,431,434],{},"Use a skeleton that matches the real component's height and layout to avoid\n",[22,432,433],{},"Cumulative Layout Shift (CLS)"," — the page should not jump when the real\ncontent appears.",[10,436,438],{"id":437},"route-based-splitting-with-react-router","Route-based splitting with React Router",[15,440,441],{},"Route-based splitting is the highest-leverage change you can make. Each route is\na natural isolation boundary; users rarely visit every route in a session, so\nthere is no reason to ship every route's code on the first load.",[54,443,445],{"className":56,"code":444,"language":58,"meta":59,"style":59},"import { lazy, Suspense } from 'react'\nimport { BrowserRouter, Routes, Route } from 'react-router-dom'\n\nconst Home      = lazy(() => import(\u002F* webpackChunkName: \"home\"      *\u002F '.\u002Fpages\u002FHome'))\nconst Dashboard = lazy(() => import(\u002F* webpackChunkName: \"dashboard\" *\u002F '.\u002Fpages\u002FDashboard'))\nconst Settings  = lazy(() => import(\u002F* webpackChunkName: \"settings\"  *\u002F '.\u002Fpages\u002FSettings'))\n\nfunction App() {\n  return (\n    \u003CBrowserRouter>\n      {\u002F* One Suspense wraps all routes — each route chunk is fetched on navigate *\u002F}\n      \u003CSuspense fallback={\u003CPageSpinner \u002F>}>\n        \u003CRoutes>\n          \u003CRoute path=\"\u002F\"          element={\u003CHome \u002F>} \u002F>\n          \u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboard \u002F>} \u002F>\n          \u003CRoute path=\"\u002Fsettings\"  element={\u003CSettings \u002F>} \u002F>\n        \u003C\u002FRoutes>\n      \u003C\u002FSuspense>\n    \u003C\u002FBrowserRouter>\n  )\n}\n",[42,446,447,457,469,473,501,527,555,559,567,573,582,592,608,618,647,671,696,706,716,725,730],{"__ignoreMap":59},[63,448,449,451,453,455],{"class":65,"line":66},[63,450,70],{"class":69},[63,452,74],{"class":73},[63,454,77],{"class":69},[63,456,81],{"class":80},[63,458,459,461,464,466],{"class":65,"line":84},[63,460,70],{"class":69},[63,462,463],{"class":73}," { BrowserRouter, Routes, Route } ",[63,465,77],{"class":69},[63,467,468],{"class":80}," 'react-router-dom'\n",[63,470,471],{"class":65,"line":91},[63,472,88],{"emptyLinePlaceholder":87},[63,474,475,477,480,483,485,487,489,491,493,496,499],{"class":65,"line":98},[63,476,118],{"class":69},[63,478,479],{"class":121}," Home",[63,481,482],{"class":69},"      =",[63,484,129],{"class":128},[63,486,132],{"class":73},[63,488,135],{"class":69},[63,490,138],{"class":128},[63,492,141],{"class":73},[63,494,495],{"class":94},"\u002F* webpackChunkName: \"home\"      *\u002F",[63,497,498],{"class":80}," '.\u002Fpages\u002FHome'",[63,500,147],{"class":73},[63,502,503,505,507,509,511,513,515,517,519,522,525],{"class":65,"line":104},[63,504,118],{"class":69},[63,506,122],{"class":121},[63,508,125],{"class":69},[63,510,129],{"class":128},[63,512,132],{"class":73},[63,514,135],{"class":69},[63,516,138],{"class":128},[63,518,141],{"class":73},[63,520,521],{"class":94},"\u002F* webpackChunkName: \"dashboard\" *\u002F",[63,523,524],{"class":80}," '.\u002Fpages\u002FDashboard'",[63,526,147],{"class":73},[63,528,529,531,534,537,539,541,543,545,547,550,553],{"class":65,"line":109},[63,530,118],{"class":69},[63,532,533],{"class":121}," Settings",[63,535,536],{"class":69},"  =",[63,538,129],{"class":128},[63,540,132],{"class":73},[63,542,135],{"class":69},[63,544,138],{"class":128},[63,546,141],{"class":73},[63,548,549],{"class":94},"\u002F* webpackChunkName: \"settings\"  *\u002F",[63,551,552],{"class":80}," '.\u002Fpages\u002FSettings'",[63,554,147],{"class":73},[63,556,557],{"class":65,"line":115},[63,558,88],{"emptyLinePlaceholder":87},[63,560,561,563,565],{"class":65,"line":150},[63,562,158],{"class":69},[63,564,161],{"class":128},[63,566,164],{"class":73},[63,568,569,571],{"class":65,"line":155},[63,570,170],{"class":69},[63,572,173],{"class":73},[63,574,575,577,580],{"class":65,"line":167},[63,576,179],{"class":73},[63,578,579],{"class":121},"BrowserRouter",[63,581,226],{"class":73},[63,583,584,587,590],{"class":65,"line":176},[63,585,586],{"class":73},"      {",[63,588,589],{"class":94},"\u002F* One Suspense wraps all routes — each route chunk is fetched on navigate *\u002F",[63,591,238],{"class":73},[63,593,594,596,598,600,602,604,606],{"class":65,"line":206},[63,595,209],{"class":73},[63,597,182],{"class":121},[63,599,185],{"class":128},[63,601,188],{"class":69},[63,603,191],{"class":73},[63,605,305],{"class":121},[63,607,308],{"class":73},[63,609,610,613,616],{"class":65,"line":218},[63,611,612],{"class":73},"        \u003C",[63,614,615],{"class":121},"Routes",[63,617,226],{"class":73},[63,619,620,623,626,629,631,634,637,639,641,644],{"class":65,"line":229},[63,621,622],{"class":73},"          \u003C",[63,624,625],{"class":121},"Route",[63,627,628],{"class":128}," path",[63,630,188],{"class":69},[63,632,633],{"class":80},"\"\u002F\"",[63,635,636],{"class":128},"          element",[63,638,188],{"class":69},[63,640,191],{"class":73},[63,642,643],{"class":121},"Home",[63,645,646],{"class":73}," \u002F>} \u002F>\n",[63,648,649,651,653,655,657,660,663,665,667,669],{"class":65,"line":235},[63,650,622],{"class":73},[63,652,625],{"class":121},[63,654,628],{"class":128},[63,656,188],{"class":69},[63,658,659],{"class":80},"\"\u002Fdashboard\"",[63,661,662],{"class":128}," element",[63,664,188],{"class":69},[63,666,191],{"class":73},[63,668,212],{"class":121},[63,670,646],{"class":73},[63,672,673,675,677,679,681,684,687,689,691,694],{"class":65,"line":424},[63,674,622],{"class":73},[63,676,625],{"class":121},[63,678,628],{"class":128},[63,680,188],{"class":69},[63,682,683],{"class":80},"\"\u002Fsettings\"",[63,685,686],{"class":128},"  element",[63,688,188],{"class":69},[63,690,191],{"class":73},[63,692,693],{"class":121},"Settings",[63,695,646],{"class":73},[63,697,699,702,704],{"class":65,"line":698},17,[63,700,701],{"class":73},"        \u003C\u002F",[63,703,615],{"class":121},[63,705,226],{"class":73},[63,707,709,712,714],{"class":65,"line":708},18,[63,710,711],{"class":73},"      \u003C\u002F",[63,713,182],{"class":121},[63,715,226],{"class":73},[63,717,719,721,723],{"class":65,"line":718},19,[63,720,221],{"class":73},[63,722,579],{"class":121},[63,724,226],{"class":73},[63,726,728],{"class":65,"line":727},20,[63,729,232],{"class":73},[63,731,733],{"class":65,"line":732},21,[63,734,238],{"class":73},[15,736,737,738,741],{},"The ",[42,739,740],{},"\u002F* webpackChunkName *\u002F"," magic comment gives each chunk a human-readable\nfilename in the build output, which makes bundle analysis and production error\nlogs far easier to read.",[10,743,745],{"id":744},"error-boundaries-handling-chunk-load-failures","Error boundaries: handling chunk load failures",[15,747,271,748,750,751,755,756,759],{},[42,749,251],{}," boundary only catches the ",[752,753,754],"em",{},"pending"," state. If a chunk fetch fails\n— due to a network error, a 404 after a deploy, or the user going offline —\nReact propagates an error up the tree. You need an ",[22,757,758],{},"Error Boundary"," to\nintercept it and give the user a recovery path.",[54,761,763],{"className":56,"code":762,"language":58,"meta":59,"style":59},"import { Component } from 'react'\n\nclass ChunkErrorBoundary extends Component {\n  state = { hasError: false }\n\n  static getDerivedStateFromError() {\n    return { hasError: true }\n  }\n\n  render() {\n    if (this.state.hasError) {\n      return (\n        \u003Cdiv>\n          \u003Cp>Failed to load. Check your connection and try again.\u003C\u002Fp>\n          \u003Cbutton onClick={() => this.setState({ hasError: false })}>\n            Retry\n          \u003C\u002Fbutton>\n        \u003C\u002Fdiv>\n      )\n    }\n    return this.props.children\n  }\n}\n\n\u002F\u002F Error boundary wraps Suspense, not the other way around\n\u003CChunkErrorBoundary>\n  \u003CSuspense fallback={\u003CSpinner \u002F>}>\n    \u003CLazyDashboard \u002F>\n  \u003C\u002FSuspense>\n\u003C\u002FChunkErrorBoundary>\n",[42,764,765,776,780,797,814,818,828,840,845,849,856,870,877,885,898,932,937,946,954,959,964,973,978,983,988,994,1004,1022,1032,1041],{"__ignoreMap":59},[63,766,767,769,772,774],{"class":65,"line":66},[63,768,70],{"class":69},[63,770,771],{"class":73}," { Component } ",[63,773,77],{"class":69},[63,775,81],{"class":80},[63,777,778],{"class":65,"line":84},[63,779,88],{"emptyLinePlaceholder":87},[63,781,782,785,788,791,794],{"class":65,"line":91},[63,783,784],{"class":69},"class",[63,786,787],{"class":128}," ChunkErrorBoundary",[63,789,790],{"class":69}," extends",[63,792,793],{"class":128}," Component",[63,795,796],{"class":73}," {\n",[63,798,799,803,805,808,811],{"class":65,"line":98},[63,800,802],{"class":801},"s4XuR","  state",[63,804,125],{"class":69},[63,806,807],{"class":73}," { hasError: ",[63,809,810],{"class":121},"false",[63,812,813],{"class":73}," }\n",[63,815,816],{"class":65,"line":104},[63,817,88],{"emptyLinePlaceholder":87},[63,819,820,823,826],{"class":65,"line":109},[63,821,822],{"class":69},"  static",[63,824,825],{"class":128}," getDerivedStateFromError",[63,827,164],{"class":73},[63,829,830,833,835,838],{"class":65,"line":115},[63,831,832],{"class":69},"    return",[63,834,807],{"class":73},[63,836,837],{"class":121},"true",[63,839,813],{"class":73},[63,841,842],{"class":65,"line":150},[63,843,844],{"class":73},"  }\n",[63,846,847],{"class":65,"line":155},[63,848,88],{"emptyLinePlaceholder":87},[63,850,851,854],{"class":65,"line":167},[63,852,853],{"class":128},"  render",[63,855,164],{"class":73},[63,857,858,861,864,867],{"class":65,"line":176},[63,859,860],{"class":69},"    if",[63,862,863],{"class":73}," (",[63,865,866],{"class":121},"this",[63,868,869],{"class":73},".state.hasError) {\n",[63,871,872,875],{"class":65,"line":206},[63,873,874],{"class":69},"      return",[63,876,173],{"class":73},[63,878,879,881,883],{"class":65,"line":218},[63,880,612],{"class":73},[63,882,195],{"class":194},[63,884,226],{"class":73},[63,886,887,889,891,894,896],{"class":65,"line":229},[63,888,622],{"class":73},[63,890,15],{"class":194},[63,892,893],{"class":73},">Failed to load. Check your connection and try again.\u003C\u002F",[63,895,15],{"class":194},[63,897,226],{"class":73},[63,899,900,902,905,908,910,913,915,918,921,924,927,929],{"class":65,"line":235},[63,901,622],{"class":73},[63,903,904],{"class":194},"button",[63,906,907],{"class":128}," onClick",[63,909,188],{"class":69},[63,911,912],{"class":73},"{() ",[63,914,135],{"class":69},[63,916,917],{"class":121}," this",[63,919,920],{"class":73},".",[63,922,923],{"class":128},"setState",[63,925,926],{"class":73},"({ hasError: ",[63,928,810],{"class":121},[63,930,931],{"class":73}," })}>\n",[63,933,934],{"class":65,"line":424},[63,935,936],{"class":73},"            Retry\n",[63,938,939,942,944],{"class":65,"line":698},[63,940,941],{"class":73},"          \u003C\u002F",[63,943,904],{"class":194},[63,945,226],{"class":73},[63,947,948,950,952],{"class":65,"line":708},[63,949,701],{"class":73},[63,951,195],{"class":194},[63,953,226],{"class":73},[63,955,956],{"class":65,"line":718},[63,957,958],{"class":73},"      )\n",[63,960,961],{"class":65,"line":727},[63,962,963],{"class":73},"    }\n",[63,965,966,968,970],{"class":65,"line":732},[63,967,832],{"class":69},[63,969,917],{"class":121},[63,971,972],{"class":73},".props.children\n",[63,974,976],{"class":65,"line":975},22,[63,977,844],{"class":73},[63,979,981],{"class":65,"line":980},23,[63,982,238],{"class":73},[63,984,986],{"class":65,"line":985},24,[63,987,88],{"emptyLinePlaceholder":87},[63,989,991],{"class":65,"line":990},25,[63,992,993],{"class":94},"\u002F\u002F Error boundary wraps Suspense, not the other way around\n",[63,995,997,999,1002],{"class":65,"line":996},26,[63,998,294],{"class":73},[63,1000,1001],{"class":121},"ChunkErrorBoundary",[63,1003,226],{"class":73},[63,1005,1007,1009,1011,1013,1015,1017,1020],{"class":65,"line":1006},27,[63,1008,313],{"class":73},[63,1010,182],{"class":121},[63,1012,185],{"class":128},[63,1014,188],{"class":69},[63,1016,191],{"class":73},[63,1018,1019],{"class":121},"Spinner",[63,1021,308],{"class":73},[63,1023,1025,1027,1030],{"class":65,"line":1024},28,[63,1026,179],{"class":73},[63,1028,1029],{"class":121},"LazyDashboard",[63,1031,215],{"class":73},[63,1033,1035,1037,1039],{"class":65,"line":1034},29,[63,1036,380],{"class":73},[63,1038,182],{"class":121},[63,1040,226],{"class":73},[63,1042,1044,1046,1048],{"class":65,"line":1043},30,[63,1045,332],{"class":73},[63,1047,1001],{"class":121},[63,1049,226],{"class":73},[15,1051,1052,1053,1056],{},"The library ",[42,1054,1055],{},"react-error-boundary"," provides a hook-friendly version of this\npattern if you want to avoid writing class components.",[10,1058,1060],{"id":1059},"prefetching-loading-before-the-user-asks","Prefetching: loading before the user asks",[15,1062,1063,1064,1067,1068,1070],{},"Once your initial bundle is lean, you can use idle time to ",[22,1065,1066],{},"prefetch"," chunks\nthe user is likely to need next. Because the browser caches ",[42,1069,48],{}," results\nby URL, calling the same import path before React.lazy needs it means the chunk\nis already available when navigation happens — the Suspense fallback is skipped\nentirely.",[54,1072,1074],{"className":56,"code":1073,"language":58,"meta":59,"style":59},"const Dashboard = lazy(() => import('.\u002Fpages\u002FDashboard'))\n\nfunction NavLink() {\n  \u002F\u002F Trigger the fetch on hover — ~100–300 ms head start before the click\n  const prefetch = () => import('.\u002Fpages\u002FDashboard')\n\n  return (\n    \u003Ca href=\"\u002Fdashboard\" onMouseEnter={prefetch} onFocus={prefetch}>\n      Dashboard\n    \u003C\u002Fa>\n  )\n}\n\n\u002F\u002F Or prefetch everything after the page is idle\nif (typeof requestIdleCallback !== 'undefined') {\n  requestIdleCallback(() => {\n    import('.\u002Fpages\u002FDashboard')\n    import('.\u002Fpages\u002FSettings')\n  })\n}\n",[42,1075,1076,1099,1103,1112,1117,1141,1145,1151,1181,1186,1194,1198,1202,1206,1211,1233,1244,1255,1266,1271],{"__ignoreMap":59},[63,1077,1078,1080,1082,1084,1086,1088,1090,1092,1094,1097],{"class":65,"line":66},[63,1079,118],{"class":69},[63,1081,122],{"class":121},[63,1083,125],{"class":69},[63,1085,129],{"class":128},[63,1087,132],{"class":73},[63,1089,135],{"class":69},[63,1091,138],{"class":69},[63,1093,141],{"class":73},[63,1095,1096],{"class":80},"'.\u002Fpages\u002FDashboard'",[63,1098,147],{"class":73},[63,1100,1101],{"class":65,"line":84},[63,1102,88],{"emptyLinePlaceholder":87},[63,1104,1105,1107,1110],{"class":65,"line":91},[63,1106,158],{"class":69},[63,1108,1109],{"class":128}," NavLink",[63,1111,164],{"class":73},[63,1113,1114],{"class":65,"line":98},[63,1115,1116],{"class":94},"  \u002F\u002F Trigger the fetch on hover — ~100–300 ms head start before the click\n",[63,1118,1119,1122,1125,1127,1130,1132,1134,1136,1138],{"class":65,"line":104},[63,1120,1121],{"class":69},"  const",[63,1123,1124],{"class":128}," prefetch",[63,1126,125],{"class":69},[63,1128,1129],{"class":73}," () ",[63,1131,135],{"class":69},[63,1133,138],{"class":69},[63,1135,141],{"class":73},[63,1137,1096],{"class":80},[63,1139,1140],{"class":73},")\n",[63,1142,1143],{"class":65,"line":109},[63,1144,88],{"emptyLinePlaceholder":87},[63,1146,1147,1149],{"class":65,"line":115},[63,1148,170],{"class":69},[63,1150,173],{"class":73},[63,1152,1153,1155,1158,1161,1163,1165,1168,1170,1173,1176,1178],{"class":65,"line":150},[63,1154,179],{"class":73},[63,1156,1157],{"class":194},"a",[63,1159,1160],{"class":128}," href",[63,1162,188],{"class":69},[63,1164,659],{"class":80},[63,1166,1167],{"class":128}," onMouseEnter",[63,1169,188],{"class":69},[63,1171,1172],{"class":73},"{prefetch} ",[63,1174,1175],{"class":128},"onFocus",[63,1177,188],{"class":69},[63,1179,1180],{"class":73},"{prefetch}>\n",[63,1182,1183],{"class":65,"line":155},[63,1184,1185],{"class":73},"      Dashboard\n",[63,1187,1188,1190,1192],{"class":65,"line":167},[63,1189,221],{"class":73},[63,1191,1157],{"class":194},[63,1193,226],{"class":73},[63,1195,1196],{"class":65,"line":176},[63,1197,232],{"class":73},[63,1199,1200],{"class":65,"line":206},[63,1201,238],{"class":73},[63,1203,1204],{"class":65,"line":218},[63,1205,88],{"emptyLinePlaceholder":87},[63,1207,1208],{"class":65,"line":229},[63,1209,1210],{"class":94},"\u002F\u002F Or prefetch everything after the page is idle\n",[63,1212,1213,1216,1218,1221,1224,1227,1230],{"class":65,"line":235},[63,1214,1215],{"class":69},"if",[63,1217,863],{"class":73},[63,1219,1220],{"class":69},"typeof",[63,1222,1223],{"class":73}," requestIdleCallback ",[63,1225,1226],{"class":69},"!==",[63,1228,1229],{"class":80}," 'undefined'",[63,1231,1232],{"class":73},") {\n",[63,1234,1235,1238,1240,1242],{"class":65,"line":424},[63,1236,1237],{"class":128},"  requestIdleCallback",[63,1239,132],{"class":73},[63,1241,135],{"class":69},[63,1243,796],{"class":73},[63,1245,1246,1249,1251,1253],{"class":65,"line":698},[63,1247,1248],{"class":69},"    import",[63,1250,141],{"class":73},[63,1252,1096],{"class":80},[63,1254,1140],{"class":73},[63,1256,1257,1259,1261,1264],{"class":65,"line":708},[63,1258,1248],{"class":69},[63,1260,141],{"class":73},[63,1262,1263],{"class":80},"'.\u002Fpages\u002FSettings'",[63,1265,1140],{"class":73},[63,1267,1268],{"class":65,"line":718},[63,1269,1270],{"class":73},"  })\n",[63,1272,1273],{"class":65,"line":727},[63,1274,238],{"class":73},[15,1276,1277,1278,1281,1282,1285],{},"Webpack's ",[42,1279,1280],{},"\u002F* webpackPrefetch: true *\u002F"," magic comment automates this by emitting\na ",[42,1283,1284],{},"\u003Clink rel=\"prefetch\">"," tag in the HTML, so the browser handles it natively\nduring idle time.",[10,1287,1289],{"id":1288},"measuring-the-impact-with-bundle-analysis","Measuring the impact with bundle analysis",[15,1291,1292],{},"You cannot optimize what you cannot see. Run bundle analysis before and after\nsplitting to confirm actual savings.",[54,1294,1298],{"className":1295,"code":1296,"language":1297,"meta":59,"style":59},"language-bash shiki shiki-themes github-light github-dark","# Vite — rollup-plugin-visualizer\n# Add to vite.config.ts:\n# import { visualizer } from 'rollup-plugin-visualizer'\n# plugins: [react(), visualizer({ open: true, gzipSize: true })]\nnpm run build\n\n# Webpack — webpack-bundle-analyzer\nnpx webpack --profile --json > stats.json\nnpx webpack-bundle-analyzer stats.json\n\n# Check chunk sizes in the build output\n# dist\u002Fassets\u002Findex-3f2a.js      → 87 KB  (initial)\n# dist\u002Fassets\u002Fdashboard-9c1b.js  → 142 KB (on demand ✓)\n# dist\u002Fassets\u002Freports-4d7e.js    → 198 KB (on demand ✓)\n","bash",[42,1299,1300,1305,1310,1315,1320,1331,1335,1340,1360,1369,1373,1378,1383,1388],{"__ignoreMap":59},[63,1301,1302],{"class":65,"line":66},[63,1303,1304],{"class":94},"# Vite — rollup-plugin-visualizer\n",[63,1306,1307],{"class":65,"line":84},[63,1308,1309],{"class":94},"# Add to vite.config.ts:\n",[63,1311,1312],{"class":65,"line":91},[63,1313,1314],{"class":94},"# import { visualizer } from 'rollup-plugin-visualizer'\n",[63,1316,1317],{"class":65,"line":98},[63,1318,1319],{"class":94},"# plugins: [react(), visualizer({ open: true, gzipSize: true })]\n",[63,1321,1322,1325,1328],{"class":65,"line":104},[63,1323,1324],{"class":128},"npm",[63,1326,1327],{"class":80}," run",[63,1329,1330],{"class":80}," build\n",[63,1332,1333],{"class":65,"line":109},[63,1334,88],{"emptyLinePlaceholder":87},[63,1336,1337],{"class":65,"line":115},[63,1338,1339],{"class":94},"# Webpack — webpack-bundle-analyzer\n",[63,1341,1342,1345,1348,1351,1354,1357],{"class":65,"line":150},[63,1343,1344],{"class":128},"npx",[63,1346,1347],{"class":80}," webpack",[63,1349,1350],{"class":121}," --profile",[63,1352,1353],{"class":121}," --json",[63,1355,1356],{"class":69}," >",[63,1358,1359],{"class":80}," stats.json\n",[63,1361,1362,1364,1367],{"class":65,"line":155},[63,1363,1344],{"class":128},[63,1365,1366],{"class":80}," webpack-bundle-analyzer",[63,1368,1359],{"class":80},[63,1370,1371],{"class":65,"line":167},[63,1372,88],{"emptyLinePlaceholder":87},[63,1374,1375],{"class":65,"line":176},[63,1376,1377],{"class":94},"# Check chunk sizes in the build output\n",[63,1379,1380],{"class":65,"line":206},[63,1381,1382],{"class":94},"# dist\u002Fassets\u002Findex-3f2a.js      → 87 KB  (initial)\n",[63,1384,1385],{"class":65,"line":218},[63,1386,1387],{"class":94},"# dist\u002Fassets\u002Fdashboard-9c1b.js  → 142 KB (on demand ✓)\n",[63,1389,1390],{"class":65,"line":229},[63,1391,1392],{"class":94},"# dist\u002Fassets\u002Freports-4d7e.js    → 198 KB (on demand ✓)\n",[15,1394,1395,1396,1399,1400,1403,1404,1407],{},"Also check the ",[22,1397,1398],{},"Network tab"," in DevTools with CPU and network throttling\nenabled — confirm that on-demand chunks appear as separate requests timed to\nnavigation, not on the initial page load. Set a bundle size budget in CI (via\n",[42,1401,1402],{},"bundlesize"," or Vite's ",[42,1405,1406],{},"build.chunkSizeWarningLimit",") to catch regressions\nbefore they reach production.",[10,1409,1411],{"id":1410},"key-takeaways","Key takeaways",[1413,1414,1415,1425,1431,1438,1444,1450],"ul",{},[1416,1417,1418,1419,1421,1422,1424],"li",{},"Use ",[42,1420,44],{}," + ",[42,1423,48],{}," to split any component into its own chunk.",[1416,1426,1427,1428,1430],{},"Always wrap lazy components in ",[42,1429,251],{}," and pair with an Error Boundary.",[1416,1432,1433,1434,1437],{},"Start with ",[22,1435,1436],{},"route-based splitting"," — it gives the biggest wins for the least\ncomplexity.",[1416,1439,1440,1443],{},[22,1441,1442],{},"Name your chunks"," with magic comments to make build output readable.",[1416,1445,1446,1449],{},[22,1447,1448],{},"Prefetch"," on hover or in idle time to make lazy loading invisible to the\nuser.",[1416,1451,1452],{},"Measure with a bundle analyzer before and after — and again in CI to prevent\nregressions.",[1454,1455,1456],"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 pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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}",{"title":59,"searchDepth":84,"depth":84,"links":1458},[1459,1460,1461,1462,1463,1464,1465,1466],{"id":12,"depth":84,"text":13},{"id":36,"depth":84,"text":37},{"id":259,"depth":84,"text":260},{"id":437,"depth":84,"text":438},{"id":744,"depth":84,"text":745},{"id":1059,"depth":84,"text":1060},{"id":1288,"depth":84,"text":1289},{"id":1410,"depth":84,"text":1411},"React code splitting and lazy loading interview questions — React.lazy, dynamic imports, Suspense boundaries, route-based splitting, bundle optimization, and prefetching strategies.","medium","md","React","react",{},"\u002Fblog\u002Freact-code-splitting-lazy-guide","\u002Freact\u002Frendering-and-performance\u002Fcode-splitting-lazy",{"title":5,"description":1467},"blog\u002Freact-code-splitting-lazy-guide","Code Splitting and Lazy Loading","Rendering and Performance","rendering-and-performance","2026-06-24","USqdLr4-aNN9r-cffvcZFVg9jVgkhVnHC240phfxGSM",1782244083288]