[{"data":1,"prerenderedAt":7420},["ShallowReactive",2],{"page-\u002Freact\u002F19-routing-react-router":3},{"id":4,"title":5,"body":6,"description":7413,"extension":7414,"meta":7415,"navigation":57,"path":7416,"seo":7417,"stem":7418,"__hash__":7419},"content\u002Freact\u002F19-routing-react-router.md","19 — Routing with React Router",{"type":7,"value":8,"toc":7391},"minimark",[9,13,18,199,203,545,549,851,855,1269,1273,1615,1620,2031,2035,2481,2485,2786,2790,3325,3329,3804,3808,3990,3994,4719,4723,6236,6240,6641,6645,6778,6782,6952,6956,6967,7114,7246,7250,7387],[10,11,5],"h1",{"id":12},"_19-routing-with-react-router",[14,15,17],"h2",{"id":16},"router-setup-createbrowserrouter-routerprovider","Router Setup — createBrowserRouter + RouterProvider",[19,20,23],"code-wrapper",{"filename":21,"language":22},"router_setup.js","javascript",[24,25,29],"pre",{"className":26,"code":27,"language":22,"meta":28,"style":28},"language-javascript shiki shiki-themes github-light github-dark","import { createBrowserRouter, RouterProvider } from 'react-router-dom'\n\n\u002F\u002F v6.4+ data router: single config object tree, rendered once at app root.\n\u002F\u002F The older \u003CBrowserRouter>\u002F\u003CRoutes>\u002F\u003CRoute> JSX API still works but lacks\n\u002F\u002F loaders, actions, and automatic error boundaries — prefer the data router.\nconst router = createBrowserRouter([\n  { path: '\u002F', element: \u003CHomePage \u002F> },\n  { path: '\u002Fabout', element: \u003CAboutPage \u002F> },\n  { path: '*', element: \u003CNotFound \u002F> },  \u002F\u002F splat — matches anything unmatched\n])\n\nfunction App() {\n  return \u003CRouterProvider router={router} \u002F>\n}\n","",[30,31,32,52,59,66,72,78,98,116,131,150,156,161,173,193],"code",{"__ignoreMap":28},[33,34,37,41,45,48],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"svdQ7","import",[33,42,44],{"class":43},"ssxIu"," { createBrowserRouter, RouterProvider } ",[33,46,47],{"class":39},"from",[33,49,51],{"class":50},"sJ6F3"," 'react-router-dom'\n",[33,53,55],{"class":35,"line":54},2,[33,56,58],{"emptyLinePlaceholder":57},true,"\n",[33,60,62],{"class":35,"line":61},3,[33,63,65],{"class":64},"sdCPZ","\u002F\u002F v6.4+ data router: single config object tree, rendered once at app root.\n",[33,67,69],{"class":35,"line":68},4,[33,70,71],{"class":64},"\u002F\u002F The older \u003CBrowserRouter>\u002F\u003CRoutes>\u002F\u003CRoute> JSX API still works but lacks\n",[33,73,75],{"class":35,"line":74},5,[33,76,77],{"class":64},"\u002F\u002F loaders, actions, and automatic error boundaries — prefer the data router.\n",[33,79,81,84,88,91,95],{"class":35,"line":80},6,[33,82,83],{"class":39},"const",[33,85,87],{"class":86},"snvgF"," router",[33,89,90],{"class":39}," =",[33,92,94],{"class":93},"sIsaT"," createBrowserRouter",[33,96,97],{"class":43},"([\n",[33,99,101,104,107,110,113],{"class":35,"line":100},7,[33,102,103],{"class":43},"  { path: ",[33,105,106],{"class":50},"'\u002F'",[33,108,109],{"class":43},", element: \u003C",[33,111,112],{"class":86},"HomePage",[33,114,115],{"class":43}," \u002F> },\n",[33,117,119,121,124,126,129],{"class":35,"line":118},8,[33,120,103],{"class":43},[33,122,123],{"class":50},"'\u002Fabout'",[33,125,109],{"class":43},[33,127,128],{"class":86},"AboutPage",[33,130,115],{"class":43},[33,132,134,136,139,141,144,147],{"class":35,"line":133},9,[33,135,103],{"class":43},[33,137,138],{"class":50},"'*'",[33,140,109],{"class":43},[33,142,143],{"class":86},"NotFound",[33,145,146],{"class":43}," \u002F> },  ",[33,148,149],{"class":64},"\u002F\u002F splat — matches anything unmatched\n",[33,151,153],{"class":35,"line":152},10,[33,154,155],{"class":43},"])\n",[33,157,159],{"class":35,"line":158},11,[33,160,58],{"emptyLinePlaceholder":57},[33,162,164,167,170],{"class":35,"line":163},12,[33,165,166],{"class":39},"function",[33,168,169],{"class":93}," App",[33,171,172],{"class":43},"() {\n",[33,174,176,179,182,185,187,190],{"class":35,"line":175},13,[33,177,178],{"class":39},"  return",[33,180,181],{"class":43}," \u003C",[33,183,184],{"class":86},"RouterProvider",[33,186,87],{"class":93},[33,188,189],{"class":39},"=",[33,191,192],{"class":43},"{router} \u002F>\n",[33,194,196],{"class":35,"line":195},14,[33,197,198],{"class":43},"}\n",[14,200,202],{"id":201},"navigation-link-vs-usenavigate","Navigation — Link vs useNavigate",[19,204,206],{"filename":205,"language":22},"navigation.js",[24,207,209],{"className":26,"code":208,"language":22,"meta":28,"style":28},"import { Link, NavLink, useNavigate } from 'react-router-dom'\n\nfunction ProductCard({ product }) {\n  const navigate = useNavigate()\n\n  async function handlePurchase() {\n    const order = await processOrder(product.id)\n    \u002F\u002F Imperative navigation — only after an async action completes.\n    \u002F\u002F Pass state for the destination to read via useLocation().\n    navigate(`\u002Forders\u002F${order.id}`, { replace: true, state: { from: 'purchase' } })\n  }\n\n  return (\n    \u003Cdiv>\n      {\u002F* \u003CLink> preserves \u003Ca> affordances: middle-click, right-click, ctrl-click *\u002F}\n      \u003CLink to={`\u002Fproducts\u002F${product.id}`}>{product.name}\u003C\u002FLink>\n      \u003Cbutton onClick={handlePurchase}>Buy Now\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F NavLink adds active-class styling via a render-prop or className function.\n\u002F\u002F `end` prevents prefix-matching: without it, to=\"\u002F\" matches EVERY route.\n\u003CNavLink to=\"\u002F\" end className={({ isActive }) => isActive ? 'active' : ''}>\n  Home\n\u003C\u002FNavLink>\n",[30,210,211,222,226,243,259,263,276,295,300,305,343,348,352,359,371,382,417,437,447,453,458,463,469,475,529,535],{"__ignoreMap":28},[33,212,213,215,218,220],{"class":35,"line":36},[33,214,40],{"class":39},[33,216,217],{"class":43}," { Link, NavLink, useNavigate } ",[33,219,47],{"class":39},[33,221,51],{"class":50},[33,223,224],{"class":35,"line":54},[33,225,58],{"emptyLinePlaceholder":57},[33,227,228,230,233,236,240],{"class":35,"line":61},[33,229,166],{"class":39},[33,231,232],{"class":93}," ProductCard",[33,234,235],{"class":43},"({ ",[33,237,239],{"class":238},"sCrzJ","product",[33,241,242],{"class":43}," }) {\n",[33,244,245,248,251,253,256],{"class":35,"line":68},[33,246,247],{"class":39},"  const",[33,249,250],{"class":86}," navigate",[33,252,90],{"class":39},[33,254,255],{"class":93}," useNavigate",[33,257,258],{"class":43},"()\n",[33,260,261],{"class":35,"line":74},[33,262,58],{"emptyLinePlaceholder":57},[33,264,265,268,271,274],{"class":35,"line":80},[33,266,267],{"class":39},"  async",[33,269,270],{"class":39}," function",[33,272,273],{"class":93}," handlePurchase",[33,275,172],{"class":43},[33,277,278,281,284,286,289,292],{"class":35,"line":100},[33,279,280],{"class":39},"    const",[33,282,283],{"class":86}," order",[33,285,90],{"class":39},[33,287,288],{"class":39}," await",[33,290,291],{"class":93}," processOrder",[33,293,294],{"class":43},"(product.id)\n",[33,296,297],{"class":35,"line":118},[33,298,299],{"class":64},"    \u002F\u002F Imperative navigation — only after an async action completes.\n",[33,301,302],{"class":35,"line":133},[33,303,304],{"class":64},"    \u002F\u002F Pass state for the destination to read via useLocation().\n",[33,306,307,310,313,316,319,322,325,328,331,334,337,340],{"class":35,"line":152},[33,308,309],{"class":93},"    navigate",[33,311,312],{"class":43},"(",[33,314,315],{"class":50},"`\u002Forders\u002F${",[33,317,318],{"class":43},"order",[33,320,321],{"class":50},".",[33,323,324],{"class":43},"id",[33,326,327],{"class":50},"}`",[33,329,330],{"class":43},", { replace: ",[33,332,333],{"class":86},"true",[33,335,336],{"class":43},", state: { from: ",[33,338,339],{"class":50},"'purchase'",[33,341,342],{"class":43}," } })\n",[33,344,345],{"class":35,"line":158},[33,346,347],{"class":43},"  }\n",[33,349,350],{"class":35,"line":163},[33,351,58],{"emptyLinePlaceholder":57},[33,353,354,356],{"class":35,"line":175},[33,355,178],{"class":39},[33,357,358],{"class":43}," (\n",[33,360,361,364,368],{"class":35,"line":195},[33,362,363],{"class":43},"    \u003C",[33,365,367],{"class":366},"sk71V","div",[33,369,370],{"class":43},">\n",[33,372,374,377,380],{"class":35,"line":373},15,[33,375,376],{"class":43},"      {",[33,378,379],{"class":64},"\u002F* \u003CLink> preserves \u003Ca> affordances: middle-click, right-click, ctrl-click *\u002F",[33,381,198],{"class":43},[33,383,385,388,391,394,396,399,402,404,406,408,410,413,415],{"class":35,"line":384},16,[33,386,387],{"class":43},"      \u003C",[33,389,390],{"class":86},"Link",[33,392,393],{"class":93}," to",[33,395,189],{"class":39},[33,397,398],{"class":43},"{",[33,400,401],{"class":50},"`\u002Fproducts\u002F${",[33,403,239],{"class":43},[33,405,321],{"class":50},[33,407,324],{"class":43},[33,409,327],{"class":50},[33,411,412],{"class":43},"}>{product.name}\u003C\u002F",[33,414,390],{"class":86},[33,416,370],{"class":43},[33,418,420,422,425,428,430,433,435],{"class":35,"line":419},17,[33,421,387],{"class":43},[33,423,424],{"class":366},"button",[33,426,427],{"class":93}," onClick",[33,429,189],{"class":39},[33,431,432],{"class":43},"{handlePurchase}>Buy Now\u003C\u002F",[33,434,424],{"class":366},[33,436,370],{"class":43},[33,438,440,443,445],{"class":35,"line":439},18,[33,441,442],{"class":43},"    \u003C\u002F",[33,444,367],{"class":366},[33,446,370],{"class":43},[33,448,450],{"class":35,"line":449},19,[33,451,452],{"class":43},"  )\n",[33,454,456],{"class":35,"line":455},20,[33,457,198],{"class":43},[33,459,461],{"class":35,"line":460},21,[33,462,58],{"emptyLinePlaceholder":57},[33,464,466],{"class":35,"line":465},22,[33,467,468],{"class":64},"\u002F\u002F NavLink adds active-class styling via a render-prop or className function.\n",[33,470,472],{"class":35,"line":471},23,[33,473,474],{"class":64},"\u002F\u002F `end` prevents prefix-matching: without it, to=\"\u002F\" matches EVERY route.\n",[33,476,478,481,484,486,488,491,494,497,499,502,505,508,511,514,517,520,523,526],{"class":35,"line":477},24,[33,479,480],{"class":43},"\u003C",[33,482,483],{"class":86},"NavLink",[33,485,393],{"class":93},[33,487,189],{"class":39},[33,489,490],{"class":50},"\"\u002F\"",[33,492,493],{"class":93}," end",[33,495,496],{"class":93}," className",[33,498,189],{"class":39},[33,500,501],{"class":43},"{({ ",[33,503,504],{"class":238},"isActive",[33,506,507],{"class":43}," }) ",[33,509,510],{"class":39},"=>",[33,512,513],{"class":43}," isActive ",[33,515,516],{"class":39},"?",[33,518,519],{"class":50}," 'active'",[33,521,522],{"class":39}," :",[33,524,525],{"class":50}," ''",[33,527,528],{"class":43},"}>\n",[33,530,532],{"class":35,"line":531},25,[33,533,534],{"class":43},"  Home\n",[33,536,538,541,543],{"class":35,"line":537},26,[33,539,540],{"class":43},"\u003C\u002F",[33,542,483],{"class":86},[33,544,370],{"class":43},[14,546,548],{"id":547},"dynamic-segments-useparams","Dynamic Segments — useParams",[19,550,552],{"filename":551,"language":22},"dynamic_params.js",[24,553,555],{"className":26,"code":554,"language":22,"meta":28,"style":28},"const router = createBrowserRouter([\n  { path: '\u002Fproducts\u002F:productId', element: \u003CProductDetail \u002F> },\n  \u002F\u002F Multiple params: \u002Fusers\u002F:userId\u002Fposts\u002F:postId\n  { path: '\u002Fusers\u002F:userId\u002Fposts\u002F:postId', element: \u003CUserPost \u002F> },\n  \u002F\u002F Optional splat: \u002Ffiles\u002F* matches \u002Ffiles, \u002Ffiles\u002Fa, \u002Ffiles\u002Fa\u002Fb\u002Fc\n  { path: '\u002Ffiles\u002F*', element: \u003CFileBrowser \u002F> },\n])\n\nfunction ProductDetail() {\n  const { productId } = useParams()\n  \u002F\u002F ALWAYS a string — \u002Fproducts\u002F42 gives \"42\", not 42.\n  \u002F\u002F Common bug: if (productId === 42) — never true. Use Number(productId).\n  const { data: product, status } = useFetch(`\u002Fapi\u002Fproducts\u002F${productId}`)\n  if (status === 'loading') return \u003CSpinner \u002F>\n  if (status === 'error') return \u003CErrorState \u002F>\n  return \u003Ch1>{product.name}\u003C\u002Fh1>\n}\n\nfunction FileBrowser() {\n  const params = useParams()\n  \u002F\u002F Splat captures the rest of the path as params['*'] — e.g. \"\u002Ffiles\u002Fa\u002Fb\" → \"a\u002Fb\"\n  const filePath = params['*'] || ''\n  return \u003CFileTree path={filePath} \u002F>\n}\n",[30,556,557,569,583,588,602,607,621,625,629,638,658,663,668,707,735,757,772,776,780,789,802,807,830,847],{"__ignoreMap":28},[33,558,559,561,563,565,567],{"class":35,"line":36},[33,560,83],{"class":39},[33,562,87],{"class":86},[33,564,90],{"class":39},[33,566,94],{"class":93},[33,568,97],{"class":43},[33,570,571,573,576,578,581],{"class":35,"line":54},[33,572,103],{"class":43},[33,574,575],{"class":50},"'\u002Fproducts\u002F:productId'",[33,577,109],{"class":43},[33,579,580],{"class":86},"ProductDetail",[33,582,115],{"class":43},[33,584,585],{"class":35,"line":61},[33,586,587],{"class":64},"  \u002F\u002F Multiple params: \u002Fusers\u002F:userId\u002Fposts\u002F:postId\n",[33,589,590,592,595,597,600],{"class":35,"line":68},[33,591,103],{"class":43},[33,593,594],{"class":50},"'\u002Fusers\u002F:userId\u002Fposts\u002F:postId'",[33,596,109],{"class":43},[33,598,599],{"class":86},"UserPost",[33,601,115],{"class":43},[33,603,604],{"class":35,"line":74},[33,605,606],{"class":64},"  \u002F\u002F Optional splat: \u002Ffiles\u002F* matches \u002Ffiles, \u002Ffiles\u002Fa, \u002Ffiles\u002Fa\u002Fb\u002Fc\n",[33,608,609,611,614,616,619],{"class":35,"line":80},[33,610,103],{"class":43},[33,612,613],{"class":50},"'\u002Ffiles\u002F*'",[33,615,109],{"class":43},[33,617,618],{"class":86},"FileBrowser",[33,620,115],{"class":43},[33,622,623],{"class":35,"line":100},[33,624,155],{"class":43},[33,626,627],{"class":35,"line":118},[33,628,58],{"emptyLinePlaceholder":57},[33,630,631,633,636],{"class":35,"line":133},[33,632,166],{"class":39},[33,634,635],{"class":93}," ProductDetail",[33,637,172],{"class":43},[33,639,640,642,645,648,651,653,656],{"class":35,"line":152},[33,641,247],{"class":39},[33,643,644],{"class":43}," { ",[33,646,647],{"class":86},"productId",[33,649,650],{"class":43}," } ",[33,652,189],{"class":39},[33,654,655],{"class":93}," useParams",[33,657,258],{"class":43},[33,659,660],{"class":35,"line":158},[33,661,662],{"class":64},"  \u002F\u002F ALWAYS a string — \u002Fproducts\u002F42 gives \"42\", not 42.\n",[33,664,665],{"class":35,"line":163},[33,666,667],{"class":64},"  \u002F\u002F Common bug: if (productId === 42) — never true. Use Number(productId).\n",[33,669,670,672,674,677,680,682,685,688,690,692,695,697,700,702,704],{"class":35,"line":175},[33,671,247],{"class":39},[33,673,644],{"class":43},[33,675,676],{"class":238},"data",[33,678,679],{"class":43},": ",[33,681,239],{"class":86},[33,683,684],{"class":43},", ",[33,686,687],{"class":86},"status",[33,689,650],{"class":43},[33,691,189],{"class":39},[33,693,694],{"class":93}," useFetch",[33,696,312],{"class":43},[33,698,699],{"class":50},"`\u002Fapi\u002Fproducts\u002F${",[33,701,647],{"class":43},[33,703,327],{"class":50},[33,705,706],{"class":43},")\n",[33,708,709,712,715,718,721,724,727,729,732],{"class":35,"line":195},[33,710,711],{"class":39},"  if",[33,713,714],{"class":43}," (status ",[33,716,717],{"class":39},"===",[33,719,720],{"class":50}," 'loading'",[33,722,723],{"class":43},") ",[33,725,726],{"class":39},"return",[33,728,181],{"class":43},[33,730,731],{"class":86},"Spinner",[33,733,734],{"class":43}," \u002F>\n",[33,736,737,739,741,743,746,748,750,752,755],{"class":35,"line":373},[33,738,711],{"class":39},[33,740,714],{"class":43},[33,742,717],{"class":39},[33,744,745],{"class":50}," 'error'",[33,747,723],{"class":43},[33,749,726],{"class":39},[33,751,181],{"class":43},[33,753,754],{"class":86},"ErrorState",[33,756,734],{"class":43},[33,758,759,761,763,765,768,770],{"class":35,"line":384},[33,760,178],{"class":39},[33,762,181],{"class":43},[33,764,10],{"class":366},[33,766,767],{"class":43},">{product.name}\u003C\u002F",[33,769,10],{"class":366},[33,771,370],{"class":43},[33,773,774],{"class":35,"line":419},[33,775,198],{"class":43},[33,777,778],{"class":35,"line":439},[33,779,58],{"emptyLinePlaceholder":57},[33,781,782,784,787],{"class":35,"line":449},[33,783,166],{"class":39},[33,785,786],{"class":93}," FileBrowser",[33,788,172],{"class":43},[33,790,791,793,796,798,800],{"class":35,"line":455},[33,792,247],{"class":39},[33,794,795],{"class":86}," params",[33,797,90],{"class":39},[33,799,655],{"class":93},[33,801,258],{"class":43},[33,803,804],{"class":35,"line":460},[33,805,806],{"class":64},"  \u002F\u002F Splat captures the rest of the path as params['*'] — e.g. \"\u002Ffiles\u002Fa\u002Fb\" → \"a\u002Fb\"\n",[33,808,809,811,814,816,819,821,824,827],{"class":35,"line":465},[33,810,247],{"class":39},[33,812,813],{"class":86}," filePath",[33,815,90],{"class":39},[33,817,818],{"class":43}," params[",[33,820,138],{"class":50},[33,822,823],{"class":43},"] ",[33,825,826],{"class":39},"||",[33,828,829],{"class":50}," ''\n",[33,831,832,834,836,839,842,844],{"class":35,"line":471},[33,833,178],{"class":39},[33,835,181],{"class":43},[33,837,838],{"class":86},"FileTree",[33,840,841],{"class":93}," path",[33,843,189],{"class":39},[33,845,846],{"class":43},"{filePath} \u002F>\n",[33,848,849],{"class":35,"line":477},[33,850,198],{"class":43},[14,852,854],{"id":853},"nested-routes-and-layouts-with-outlet","Nested Routes and Layouts with Outlet",[19,856,858],{"filename":857,"language":22},"nested_routes.js",[24,859,861],{"className":26,"code":860,"language":22,"meta":28,"style":28},"const router = createBrowserRouter([\n  {\n    path: '\u002F',\n    element: \u003CRootLayout \u002F>,\n    errorElement: \u003CRootError \u002F>,  \u002F\u002F catches errors from ALL children too\n    children: [\n      { index: true, element: \u003CHomePage \u002F> },           \u002F\u002F matches \"\u002F\" exactly\n      { path: 'products', element: \u003CProductsPage \u002F> },\n      { path: 'products\u002F:productId', element: \u003CProductDetail \u002F> },\n      {\n        path: 'account',\n        element: \u003CAccountLayout \u002F>,                       \u002F\u002F nested layout\n        children: [\n          { index: true, element: \u003CAccountOverview \u002F> },  \u002F\u002F matches \"\u002Faccount\"\n          { path: 'orders', element: \u003COrderHistory \u002F> },\n          { path: 'settings', element: \u003CAccountSettings \u002F> },\n        ],\n      },\n    ],\n  },\n])\n\n\u002F\u002F RootLayout renders once and NEVER unmounts while navigating between children.\n\u002F\u002F State, scroll position, open dropdowns, audio players — all persist.\nimport { Outlet, NavLink } from 'react-router-dom'\n\nfunction RootLayout() {\n  return (\n    \u003Cdiv>\n      \u003Cnav>\n        \u003CNavLink to=\"\u002F\" end>Home\u003C\u002FNavLink>\n        \u003CNavLink to=\"\u002Fproducts\">Products\u003C\u002FNavLink>\n        \u003CNavLink to=\"\u002Faccount\">Account\u003C\u002FNavLink>\n      \u003C\u002Fnav>\n      {\u002F* Outlet = insertion point for whichever child route matched *\u002F}\n      \u003Cmain>\u003COutlet \u002F>\u003C\u002Fmain>\n      \u003CFooter \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,862,863,875,880,890,901,915,920,937,952,965,970,980,994,999,1016,1031,1045,1050,1055,1060,1065,1069,1073,1078,1083,1094,1098,1108,1115,1124,1134,1157,1178,1199,1209,1219,1240,1250,1259,1264],{"__ignoreMap":28},[33,864,865,867,869,871,873],{"class":35,"line":36},[33,866,83],{"class":39},[33,868,87],{"class":86},[33,870,90],{"class":39},[33,872,94],{"class":93},[33,874,97],{"class":43},[33,876,877],{"class":35,"line":54},[33,878,879],{"class":43},"  {\n",[33,881,882,885,887],{"class":35,"line":61},[33,883,884],{"class":43},"    path: ",[33,886,106],{"class":50},[33,888,889],{"class":43},",\n",[33,891,892,895,898],{"class":35,"line":68},[33,893,894],{"class":43},"    element: \u003C",[33,896,897],{"class":86},"RootLayout",[33,899,900],{"class":43}," \u002F>,\n",[33,902,903,906,909,912],{"class":35,"line":74},[33,904,905],{"class":43},"    errorElement: \u003C",[33,907,908],{"class":86},"RootError",[33,910,911],{"class":43}," \u002F>,  ",[33,913,914],{"class":64},"\u002F\u002F catches errors from ALL children too\n",[33,916,917],{"class":35,"line":80},[33,918,919],{"class":43},"    children: [\n",[33,921,922,925,927,929,931,934],{"class":35,"line":100},[33,923,924],{"class":43},"      { index: ",[33,926,333],{"class":86},[33,928,109],{"class":43},[33,930,112],{"class":86},[33,932,933],{"class":43}," \u002F> },           ",[33,935,936],{"class":64},"\u002F\u002F matches \"\u002F\" exactly\n",[33,938,939,942,945,947,950],{"class":35,"line":118},[33,940,941],{"class":43},"      { path: ",[33,943,944],{"class":50},"'products'",[33,946,109],{"class":43},[33,948,949],{"class":86},"ProductsPage",[33,951,115],{"class":43},[33,953,954,956,959,961,963],{"class":35,"line":133},[33,955,941],{"class":43},[33,957,958],{"class":50},"'products\u002F:productId'",[33,960,109],{"class":43},[33,962,580],{"class":86},[33,964,115],{"class":43},[33,966,967],{"class":35,"line":152},[33,968,969],{"class":43},"      {\n",[33,971,972,975,978],{"class":35,"line":158},[33,973,974],{"class":43},"        path: ",[33,976,977],{"class":50},"'account'",[33,979,889],{"class":43},[33,981,982,985,988,991],{"class":35,"line":163},[33,983,984],{"class":43},"        element: \u003C",[33,986,987],{"class":86},"AccountLayout",[33,989,990],{"class":43}," \u002F>,                       ",[33,992,993],{"class":64},"\u002F\u002F nested layout\n",[33,995,996],{"class":35,"line":175},[33,997,998],{"class":43},"        children: [\n",[33,1000,1001,1004,1006,1008,1011,1013],{"class":35,"line":195},[33,1002,1003],{"class":43},"          { index: ",[33,1005,333],{"class":86},[33,1007,109],{"class":43},[33,1009,1010],{"class":86},"AccountOverview",[33,1012,146],{"class":43},[33,1014,1015],{"class":64},"\u002F\u002F matches \"\u002Faccount\"\n",[33,1017,1018,1021,1024,1026,1029],{"class":35,"line":373},[33,1019,1020],{"class":43},"          { path: ",[33,1022,1023],{"class":50},"'orders'",[33,1025,109],{"class":43},[33,1027,1028],{"class":86},"OrderHistory",[33,1030,115],{"class":43},[33,1032,1033,1035,1038,1040,1043],{"class":35,"line":384},[33,1034,1020],{"class":43},[33,1036,1037],{"class":50},"'settings'",[33,1039,109],{"class":43},[33,1041,1042],{"class":86},"AccountSettings",[33,1044,115],{"class":43},[33,1046,1047],{"class":35,"line":419},[33,1048,1049],{"class":43},"        ],\n",[33,1051,1052],{"class":35,"line":439},[33,1053,1054],{"class":43},"      },\n",[33,1056,1057],{"class":35,"line":449},[33,1058,1059],{"class":43},"    ],\n",[33,1061,1062],{"class":35,"line":455},[33,1063,1064],{"class":43},"  },\n",[33,1066,1067],{"class":35,"line":460},[33,1068,155],{"class":43},[33,1070,1071],{"class":35,"line":465},[33,1072,58],{"emptyLinePlaceholder":57},[33,1074,1075],{"class":35,"line":471},[33,1076,1077],{"class":64},"\u002F\u002F RootLayout renders once and NEVER unmounts while navigating between children.\n",[33,1079,1080],{"class":35,"line":477},[33,1081,1082],{"class":64},"\u002F\u002F State, scroll position, open dropdowns, audio players — all persist.\n",[33,1084,1085,1087,1090,1092],{"class":35,"line":531},[33,1086,40],{"class":39},[33,1088,1089],{"class":43}," { Outlet, NavLink } ",[33,1091,47],{"class":39},[33,1093,51],{"class":50},[33,1095,1096],{"class":35,"line":537},[33,1097,58],{"emptyLinePlaceholder":57},[33,1099,1101,1103,1106],{"class":35,"line":1100},27,[33,1102,166],{"class":39},[33,1104,1105],{"class":93}," RootLayout",[33,1107,172],{"class":43},[33,1109,1111,1113],{"class":35,"line":1110},28,[33,1112,178],{"class":39},[33,1114,358],{"class":43},[33,1116,1118,1120,1122],{"class":35,"line":1117},29,[33,1119,363],{"class":43},[33,1121,367],{"class":366},[33,1123,370],{"class":43},[33,1125,1127,1129,1132],{"class":35,"line":1126},30,[33,1128,387],{"class":43},[33,1130,1131],{"class":366},"nav",[33,1133,370],{"class":43},[33,1135,1137,1140,1142,1144,1146,1148,1150,1153,1155],{"class":35,"line":1136},31,[33,1138,1139],{"class":43},"        \u003C",[33,1141,483],{"class":86},[33,1143,393],{"class":93},[33,1145,189],{"class":39},[33,1147,490],{"class":50},[33,1149,493],{"class":93},[33,1151,1152],{"class":43},">Home\u003C\u002F",[33,1154,483],{"class":86},[33,1156,370],{"class":43},[33,1158,1160,1162,1164,1166,1168,1171,1174,1176],{"class":35,"line":1159},32,[33,1161,1139],{"class":43},[33,1163,483],{"class":86},[33,1165,393],{"class":93},[33,1167,189],{"class":39},[33,1169,1170],{"class":50},"\"\u002Fproducts\"",[33,1172,1173],{"class":43},">Products\u003C\u002F",[33,1175,483],{"class":86},[33,1177,370],{"class":43},[33,1179,1181,1183,1185,1187,1189,1192,1195,1197],{"class":35,"line":1180},33,[33,1182,1139],{"class":43},[33,1184,483],{"class":86},[33,1186,393],{"class":93},[33,1188,189],{"class":39},[33,1190,1191],{"class":50},"\"\u002Faccount\"",[33,1193,1194],{"class":43},">Account\u003C\u002F",[33,1196,483],{"class":86},[33,1198,370],{"class":43},[33,1200,1202,1205,1207],{"class":35,"line":1201},34,[33,1203,1204],{"class":43},"      \u003C\u002F",[33,1206,1131],{"class":366},[33,1208,370],{"class":43},[33,1210,1212,1214,1217],{"class":35,"line":1211},35,[33,1213,376],{"class":43},[33,1215,1216],{"class":64},"\u002F* Outlet = insertion point for whichever child route matched *\u002F",[33,1218,198],{"class":43},[33,1220,1222,1224,1227,1230,1233,1236,1238],{"class":35,"line":1221},36,[33,1223,387],{"class":43},[33,1225,1226],{"class":366},"main",[33,1228,1229],{"class":43},">\u003C",[33,1231,1232],{"class":86},"Outlet",[33,1234,1235],{"class":43}," \u002F>\u003C\u002F",[33,1237,1226],{"class":366},[33,1239,370],{"class":43},[33,1241,1243,1245,1248],{"class":35,"line":1242},37,[33,1244,387],{"class":43},[33,1246,1247],{"class":86},"Footer",[33,1249,734],{"class":43},[33,1251,1253,1255,1257],{"class":35,"line":1252},38,[33,1254,442],{"class":43},[33,1256,367],{"class":366},[33,1258,370],{"class":43},[33,1260,1262],{"class":35,"line":1261},39,[33,1263,452],{"class":43},[33,1265,1267],{"class":35,"line":1266},40,[33,1268,198],{"class":43},[14,1270,1272],{"id":1271},"route-loaders-fetch-before-render","Route Loaders — Fetch Before Render",[19,1274,1276],{"filename":1275,"language":22},"loaders.js",[24,1277,1279],{"className":26,"code":1278,"language":22,"meta":28,"style":28},"\u002F\u002F Loaders run DURING navigation, before the route element renders.\n\u002F\u002F Data is available immediately via useLoaderData — no loading flash.\n\u002F\u002F This inverts fetch-on-mount: \"navigate → fetch → render with data\"\n\u002F\u002F instead of \"navigate → render spinner → fetch → re-render with data.\"\n\nconst router = createBrowserRouter([\n  {\n    path: '\u002Fproducts\u002F:productId',\n    element: \u003CProductDetail \u002F>,\n    loader: async ({ params, request }) => {\n      \u002F\u002F `request` is a Request object — has .url, .signal (AbortSignal), .headers\n      \u002F\u002F Use request.signal to cancel the fetch if the user navigates away mid-load.\n      const res = await fetch(`\u002Fapi\u002Fproducts\u002F${params.productId}`, {\n        signal: request.signal,\n      })\n      if (res.status === 404) {\n        \u002F\u002F Throwing a Response triggers the route's errorElement.\n        \u002F\u002F isRouteErrorResponse() returns true for thrown Response objects.\n        throw new Response('Product not found', { status: 404 })\n      }\n      if (!res.ok) throw new Response('Server error', { status: 500 })\n      return res.json()\n    },\n  },\n])\n\nfunction ProductDetail() {\n  const product = useLoaderData()  \u002F\u002F already resolved — no loading state needed\n  return (\n    \u003Cdiv>\n      \u003Ch1>{product.name}\u003C\u002Fh1>\n      \u003Cp>${product.price}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  )\n}\n",[30,1280,1281,1286,1291,1296,1301,1305,1317,1321,1329,1337,1365,1370,1375,1405,1410,1415,1431,1436,1441,1466,1471,1503,1516,1521,1525,1529,1533,1541,1559,1565,1573,1585,1599,1607,1611],{"__ignoreMap":28},[33,1282,1283],{"class":35,"line":36},[33,1284,1285],{"class":64},"\u002F\u002F Loaders run DURING navigation, before the route element renders.\n",[33,1287,1288],{"class":35,"line":54},[33,1289,1290],{"class":64},"\u002F\u002F Data is available immediately via useLoaderData — no loading flash.\n",[33,1292,1293],{"class":35,"line":61},[33,1294,1295],{"class":64},"\u002F\u002F This inverts fetch-on-mount: \"navigate → fetch → render with data\"\n",[33,1297,1298],{"class":35,"line":68},[33,1299,1300],{"class":64},"\u002F\u002F instead of \"navigate → render spinner → fetch → re-render with data.\"\n",[33,1302,1303],{"class":35,"line":74},[33,1304,58],{"emptyLinePlaceholder":57},[33,1306,1307,1309,1311,1313,1315],{"class":35,"line":80},[33,1308,83],{"class":39},[33,1310,87],{"class":86},[33,1312,90],{"class":39},[33,1314,94],{"class":93},[33,1316,97],{"class":43},[33,1318,1319],{"class":35,"line":100},[33,1320,879],{"class":43},[33,1322,1323,1325,1327],{"class":35,"line":118},[33,1324,884],{"class":43},[33,1326,575],{"class":50},[33,1328,889],{"class":43},[33,1330,1331,1333,1335],{"class":35,"line":133},[33,1332,894],{"class":43},[33,1334,580],{"class":86},[33,1336,900],{"class":43},[33,1338,1339,1342,1344,1347,1350,1353,1355,1358,1360,1362],{"class":35,"line":152},[33,1340,1341],{"class":93},"    loader",[33,1343,679],{"class":43},[33,1345,1346],{"class":39},"async",[33,1348,1349],{"class":43}," ({ ",[33,1351,1352],{"class":238},"params",[33,1354,684],{"class":43},[33,1356,1357],{"class":238},"request",[33,1359,507],{"class":43},[33,1361,510],{"class":39},[33,1363,1364],{"class":43}," {\n",[33,1366,1367],{"class":35,"line":158},[33,1368,1369],{"class":64},"      \u002F\u002F `request` is a Request object — has .url, .signal (AbortSignal), .headers\n",[33,1371,1372],{"class":35,"line":163},[33,1373,1374],{"class":64},"      \u002F\u002F Use request.signal to cancel the fetch if the user navigates away mid-load.\n",[33,1376,1377,1380,1383,1385,1387,1390,1392,1394,1396,1398,1400,1402],{"class":35,"line":175},[33,1378,1379],{"class":39},"      const",[33,1381,1382],{"class":86}," res",[33,1384,90],{"class":39},[33,1386,288],{"class":39},[33,1388,1389],{"class":93}," fetch",[33,1391,312],{"class":43},[33,1393,699],{"class":50},[33,1395,1352],{"class":43},[33,1397,321],{"class":50},[33,1399,647],{"class":43},[33,1401,327],{"class":50},[33,1403,1404],{"class":43},", {\n",[33,1406,1407],{"class":35,"line":195},[33,1408,1409],{"class":43},"        signal: request.signal,\n",[33,1411,1412],{"class":35,"line":373},[33,1413,1414],{"class":43},"      })\n",[33,1416,1417,1420,1423,1425,1428],{"class":35,"line":384},[33,1418,1419],{"class":39},"      if",[33,1421,1422],{"class":43}," (res.status ",[33,1424,717],{"class":39},[33,1426,1427],{"class":86}," 404",[33,1429,1430],{"class":43},") {\n",[33,1432,1433],{"class":35,"line":419},[33,1434,1435],{"class":64},"        \u002F\u002F Throwing a Response triggers the route's errorElement.\n",[33,1437,1438],{"class":35,"line":439},[33,1439,1440],{"class":64},"        \u002F\u002F isRouteErrorResponse() returns true for thrown Response objects.\n",[33,1442,1443,1446,1449,1452,1454,1457,1460,1463],{"class":35,"line":449},[33,1444,1445],{"class":39},"        throw",[33,1447,1448],{"class":39}," new",[33,1450,1451],{"class":93}," Response",[33,1453,312],{"class":43},[33,1455,1456],{"class":50},"'Product not found'",[33,1458,1459],{"class":43},", { status: ",[33,1461,1462],{"class":86},"404",[33,1464,1465],{"class":43}," })\n",[33,1467,1468],{"class":35,"line":455},[33,1469,1470],{"class":43},"      }\n",[33,1472,1473,1475,1478,1481,1484,1487,1489,1491,1493,1496,1498,1501],{"class":35,"line":460},[33,1474,1419],{"class":39},[33,1476,1477],{"class":43}," (",[33,1479,1480],{"class":39},"!",[33,1482,1483],{"class":43},"res.ok) ",[33,1485,1486],{"class":39},"throw",[33,1488,1448],{"class":39},[33,1490,1451],{"class":93},[33,1492,312],{"class":43},[33,1494,1495],{"class":50},"'Server error'",[33,1497,1459],{"class":43},[33,1499,1500],{"class":86},"500",[33,1502,1465],{"class":43},[33,1504,1505,1508,1511,1514],{"class":35,"line":465},[33,1506,1507],{"class":39},"      return",[33,1509,1510],{"class":43}," res.",[33,1512,1513],{"class":93},"json",[33,1515,258],{"class":43},[33,1517,1518],{"class":35,"line":471},[33,1519,1520],{"class":43},"    },\n",[33,1522,1523],{"class":35,"line":477},[33,1524,1064],{"class":43},[33,1526,1527],{"class":35,"line":531},[33,1528,155],{"class":43},[33,1530,1531],{"class":35,"line":537},[33,1532,58],{"emptyLinePlaceholder":57},[33,1534,1535,1537,1539],{"class":35,"line":1100},[33,1536,166],{"class":39},[33,1538,635],{"class":93},[33,1540,172],{"class":43},[33,1542,1543,1545,1548,1550,1553,1556],{"class":35,"line":1110},[33,1544,247],{"class":39},[33,1546,1547],{"class":86}," product",[33,1549,90],{"class":39},[33,1551,1552],{"class":93}," useLoaderData",[33,1554,1555],{"class":43},"()  ",[33,1557,1558],{"class":64},"\u002F\u002F already resolved — no loading state needed\n",[33,1560,1561,1563],{"class":35,"line":1117},[33,1562,178],{"class":39},[33,1564,358],{"class":43},[33,1566,1567,1569,1571],{"class":35,"line":1126},[33,1568,363],{"class":43},[33,1570,367],{"class":366},[33,1572,370],{"class":43},[33,1574,1575,1577,1579,1581,1583],{"class":35,"line":1136},[33,1576,387],{"class":43},[33,1578,10],{"class":366},[33,1580,767],{"class":43},[33,1582,10],{"class":366},[33,1584,370],{"class":43},[33,1586,1587,1589,1592,1595,1597],{"class":35,"line":1159},[33,1588,387],{"class":43},[33,1590,1591],{"class":366},"p",[33,1593,1594],{"class":43},">${product.price}\u003C\u002F",[33,1596,1591],{"class":366},[33,1598,370],{"class":43},[33,1600,1601,1603,1605],{"class":35,"line":1180},[33,1602,442],{"class":43},[33,1604,367],{"class":366},[33,1606,370],{"class":43},[33,1608,1609],{"class":35,"line":1201},[33,1610,452],{"class":43},[33,1612,1613],{"class":35,"line":1211},[33,1614,198],{"class":43},[1616,1617,1619],"h3",{"id":1618},"loader-with-parallel-fetches-and-caching","Loader with Parallel Fetches and Caching",[19,1621,1623],{"filename":1622,"language":22},"loader_parallel.js",[24,1624,1626],{"className":26,"code":1625,"language":22,"meta":28,"style":28},"\u002F\u002F Multiple independent fetches — run in parallel, not sequentially.\nasync function dashboardLoader({ params, request }) {\n  const [user, orders, recommendations] = await Promise.all([\n    fetch('\u002Fapi\u002Fme', { signal: request.signal }).then(r => r.json()),\n    fetch(`\u002Fapi\u002Fusers\u002F${params.userId}\u002Forders`, { signal: request.signal }).then(r => r.json()),\n    fetch(`\u002Fapi\u002Fusers\u002F${params.userId}\u002Frecs`, { signal: request.signal }).then(r => r.json()),\n  ])\n  return { user, orders, recommendations }\n}\n\n\u002F\u002F Manual cache: React Router doesn't deduplicate loader calls across navigations.\n\u002F\u002F A simple cache layer helps avoid re-fetching on back\u002Fforward navigation.\nconst cache = new Map()\n\nasync function cachedProductLoader({ params, request }) {\n  const key = params.productId\n  if (cache.has(key)) {\n    const cached = cache.get(key)\n    if (Date.now() - cached.ts \u003C 60_000) return cached.data  \u002F\u002F 1-minute TTL\n  }\n  const res = await fetch(`\u002Fapi\u002Fproducts\u002F${key}`, { signal: request.signal })\n  if (!res.ok) throw new Response('Error', { status: res.status })\n  const data = await res.json()\n  cache.set(key, { data, ts: Date.now() })\n  return data\n}\n",[30,1627,1628,1633,1652,1688,1720,1755,1788,1793,1800,1804,1808,1813,1818,1834,1838,1857,1869,1882,1900,1935,1939,1963,1987,2004,2020,2027],{"__ignoreMap":28},[33,1629,1630],{"class":35,"line":36},[33,1631,1632],{"class":64},"\u002F\u002F Multiple independent fetches — run in parallel, not sequentially.\n",[33,1634,1635,1637,1639,1642,1644,1646,1648,1650],{"class":35,"line":54},[33,1636,1346],{"class":39},[33,1638,270],{"class":39},[33,1640,1641],{"class":93}," dashboardLoader",[33,1643,235],{"class":43},[33,1645,1352],{"class":238},[33,1647,684],{"class":43},[33,1649,1357],{"class":238},[33,1651,242],{"class":43},[33,1653,1654,1656,1659,1662,1664,1667,1669,1672,1674,1676,1678,1681,1683,1686],{"class":35,"line":61},[33,1655,247],{"class":39},[33,1657,1658],{"class":43}," [",[33,1660,1661],{"class":86},"user",[33,1663,684],{"class":43},[33,1665,1666],{"class":86},"orders",[33,1668,684],{"class":43},[33,1670,1671],{"class":86},"recommendations",[33,1673,823],{"class":43},[33,1675,189],{"class":39},[33,1677,288],{"class":39},[33,1679,1680],{"class":86}," Promise",[33,1682,321],{"class":43},[33,1684,1685],{"class":93},"all",[33,1687,97],{"class":43},[33,1689,1690,1693,1695,1698,1701,1704,1706,1709,1712,1715,1717],{"class":35,"line":68},[33,1691,1692],{"class":93},"    fetch",[33,1694,312],{"class":43},[33,1696,1697],{"class":50},"'\u002Fapi\u002Fme'",[33,1699,1700],{"class":43},", { signal: request.signal }).",[33,1702,1703],{"class":93},"then",[33,1705,312],{"class":43},[33,1707,1708],{"class":238},"r",[33,1710,1711],{"class":39}," =>",[33,1713,1714],{"class":43}," r.",[33,1716,1513],{"class":93},[33,1718,1719],{"class":43},"()),\n",[33,1721,1722,1724,1726,1729,1731,1733,1736,1739,1741,1743,1745,1747,1749,1751,1753],{"class":35,"line":74},[33,1723,1692],{"class":93},[33,1725,312],{"class":43},[33,1727,1728],{"class":50},"`\u002Fapi\u002Fusers\u002F${",[33,1730,1352],{"class":43},[33,1732,321],{"class":50},[33,1734,1735],{"class":43},"userId",[33,1737,1738],{"class":50},"}\u002Forders`",[33,1740,1700],{"class":43},[33,1742,1703],{"class":93},[33,1744,312],{"class":43},[33,1746,1708],{"class":238},[33,1748,1711],{"class":39},[33,1750,1714],{"class":43},[33,1752,1513],{"class":93},[33,1754,1719],{"class":43},[33,1756,1757,1759,1761,1763,1765,1767,1769,1772,1774,1776,1778,1780,1782,1784,1786],{"class":35,"line":80},[33,1758,1692],{"class":93},[33,1760,312],{"class":43},[33,1762,1728],{"class":50},[33,1764,1352],{"class":43},[33,1766,321],{"class":50},[33,1768,1735],{"class":43},[33,1770,1771],{"class":50},"}\u002Frecs`",[33,1773,1700],{"class":43},[33,1775,1703],{"class":93},[33,1777,312],{"class":43},[33,1779,1708],{"class":238},[33,1781,1711],{"class":39},[33,1783,1714],{"class":43},[33,1785,1513],{"class":93},[33,1787,1719],{"class":43},[33,1789,1790],{"class":35,"line":100},[33,1791,1792],{"class":43},"  ])\n",[33,1794,1795,1797],{"class":35,"line":118},[33,1796,178],{"class":39},[33,1798,1799],{"class":43}," { user, orders, recommendations }\n",[33,1801,1802],{"class":35,"line":133},[33,1803,198],{"class":43},[33,1805,1806],{"class":35,"line":152},[33,1807,58],{"emptyLinePlaceholder":57},[33,1809,1810],{"class":35,"line":158},[33,1811,1812],{"class":64},"\u002F\u002F Manual cache: React Router doesn't deduplicate loader calls across navigations.\n",[33,1814,1815],{"class":35,"line":163},[33,1816,1817],{"class":64},"\u002F\u002F A simple cache layer helps avoid re-fetching on back\u002Fforward navigation.\n",[33,1819,1820,1822,1825,1827,1829,1832],{"class":35,"line":175},[33,1821,83],{"class":39},[33,1823,1824],{"class":86}," cache",[33,1826,90],{"class":39},[33,1828,1448],{"class":39},[33,1830,1831],{"class":93}," Map",[33,1833,258],{"class":43},[33,1835,1836],{"class":35,"line":195},[33,1837,58],{"emptyLinePlaceholder":57},[33,1839,1840,1842,1844,1847,1849,1851,1853,1855],{"class":35,"line":373},[33,1841,1346],{"class":39},[33,1843,270],{"class":39},[33,1845,1846],{"class":93}," cachedProductLoader",[33,1848,235],{"class":43},[33,1850,1352],{"class":238},[33,1852,684],{"class":43},[33,1854,1357],{"class":238},[33,1856,242],{"class":43},[33,1858,1859,1861,1864,1866],{"class":35,"line":384},[33,1860,247],{"class":39},[33,1862,1863],{"class":86}," key",[33,1865,90],{"class":39},[33,1867,1868],{"class":43}," params.productId\n",[33,1870,1871,1873,1876,1879],{"class":35,"line":419},[33,1872,711],{"class":39},[33,1874,1875],{"class":43}," (cache.",[33,1877,1878],{"class":93},"has",[33,1880,1881],{"class":43},"(key)) {\n",[33,1883,1884,1886,1889,1891,1894,1897],{"class":35,"line":439},[33,1885,280],{"class":39},[33,1887,1888],{"class":86}," cached",[33,1890,90],{"class":39},[33,1892,1893],{"class":43}," cache.",[33,1895,1896],{"class":93},"get",[33,1898,1899],{"class":43},"(key)\n",[33,1901,1902,1905,1908,1911,1914,1917,1920,1922,1925,1927,1929,1932],{"class":35,"line":449},[33,1903,1904],{"class":39},"    if",[33,1906,1907],{"class":43}," (Date.",[33,1909,1910],{"class":93},"now",[33,1912,1913],{"class":43},"() ",[33,1915,1916],{"class":39},"-",[33,1918,1919],{"class":43}," cached.ts ",[33,1921,480],{"class":39},[33,1923,1924],{"class":86}," 60_000",[33,1926,723],{"class":43},[33,1928,726],{"class":39},[33,1930,1931],{"class":43}," cached.data  ",[33,1933,1934],{"class":64},"\u002F\u002F 1-minute TTL\n",[33,1936,1937],{"class":35,"line":455},[33,1938,347],{"class":43},[33,1940,1941,1943,1945,1947,1949,1951,1953,1955,1958,1960],{"class":35,"line":460},[33,1942,247],{"class":39},[33,1944,1382],{"class":86},[33,1946,90],{"class":39},[33,1948,288],{"class":39},[33,1950,1389],{"class":93},[33,1952,312],{"class":43},[33,1954,699],{"class":50},[33,1956,1957],{"class":43},"key",[33,1959,327],{"class":50},[33,1961,1962],{"class":43},", { signal: request.signal })\n",[33,1964,1965,1967,1969,1971,1973,1975,1977,1979,1981,1984],{"class":35,"line":465},[33,1966,711],{"class":39},[33,1968,1477],{"class":43},[33,1970,1480],{"class":39},[33,1972,1483],{"class":43},[33,1974,1486],{"class":39},[33,1976,1448],{"class":39},[33,1978,1451],{"class":93},[33,1980,312],{"class":43},[33,1982,1983],{"class":50},"'Error'",[33,1985,1986],{"class":43},", { status: res.status })\n",[33,1988,1989,1991,1994,1996,1998,2000,2002],{"class":35,"line":471},[33,1990,247],{"class":39},[33,1992,1993],{"class":86}," data",[33,1995,90],{"class":39},[33,1997,288],{"class":39},[33,1999,1510],{"class":43},[33,2001,1513],{"class":93},[33,2003,258],{"class":43},[33,2005,2006,2009,2012,2015,2017],{"class":35,"line":477},[33,2007,2008],{"class":43},"  cache.",[33,2010,2011],{"class":93},"set",[33,2013,2014],{"class":43},"(key, { data, ts: Date.",[33,2016,1910],{"class":93},[33,2018,2019],{"class":43},"() })\n",[33,2021,2022,2024],{"class":35,"line":531},[33,2023,178],{"class":39},[33,2025,2026],{"class":43}," data\n",[33,2028,2029],{"class":35,"line":537},[33,2030,198],{"class":43},[14,2032,2034],{"id":2033},"route-actions-mutations","Route Actions — Mutations",[19,2036,2038],{"filename":2037,"language":22},"actions.js",[24,2039,2041],{"className":26,"code":2040,"language":22,"meta":28,"style":28},"\u002F\u002F Actions handle form submissions \u002F mutations. The data router automatically\n\u002F\u002F revalidates all active loaders after an action completes — no manual refetch.\n\nconst router = createBrowserRouter([\n  {\n    path: '\u002Fproducts\u002F:productId\u002Fedit',\n    element: \u003CEditProduct \u002F>,\n    action: async ({ params, request }) => {\n      const formData = await request.formData()\n      const res = await fetch(`\u002Fapi\u002Fproducts\u002F${params.productId}`, {\n        method: 'PUT',\n        body: JSON.stringify(Object.fromEntries(formData)),\n        headers: { 'Content-Type': 'application\u002Fjson' },\n      })\n      if (!res.ok) {\n        \u002F\u002F Return errors as data (not throw) to show inline validation errors.\n        return { errors: await res.json() }\n      }\n      return { success: true }  \u002F\u002F triggers loader revalidation automatically\n    },\n  },\n])\n\nfunction EditProduct() {\n  const product = useLoaderData()\n  const actionData = useActionData()  \u002F\u002F whatever the action returned\n  const navigation = useNavigation()  \u002F\u002F { state: 'idle' | 'submitting' | 'loading' }\n\n  return (\n    \u003CForm method=\"post\">\n      \u003Cinput name=\"name\" defaultValue={product.name} \u002F>\n      {actionData?.errors?.name && \u003Cp className=\"error\">{actionData.errors.name}\u003C\u002Fp>}\n      \u003Cbutton disabled={navigation.state !== 'idle'}>\n        {navigation.state === 'submitting' ? 'Saving…' : 'Save'}\n      \u003C\u002Fbutton>\n    \u003C\u002FForm>\n  )\n}\n",[30,2042,2043,2048,2053,2057,2069,2073,2082,2091,2114,2133,2159,2169,2191,2207,2211,2222,2227,2245,2249,2264,2268,2272,2276,2280,2289,2301,2318,2335,2339,2345,2362,2385,2412,2434,2457,2465,2473,2477],{"__ignoreMap":28},[33,2044,2045],{"class":35,"line":36},[33,2046,2047],{"class":64},"\u002F\u002F Actions handle form submissions \u002F mutations. The data router automatically\n",[33,2049,2050],{"class":35,"line":54},[33,2051,2052],{"class":64},"\u002F\u002F revalidates all active loaders after an action completes — no manual refetch.\n",[33,2054,2055],{"class":35,"line":61},[33,2056,58],{"emptyLinePlaceholder":57},[33,2058,2059,2061,2063,2065,2067],{"class":35,"line":68},[33,2060,83],{"class":39},[33,2062,87],{"class":86},[33,2064,90],{"class":39},[33,2066,94],{"class":93},[33,2068,97],{"class":43},[33,2070,2071],{"class":35,"line":74},[33,2072,879],{"class":43},[33,2074,2075,2077,2080],{"class":35,"line":80},[33,2076,884],{"class":43},[33,2078,2079],{"class":50},"'\u002Fproducts\u002F:productId\u002Fedit'",[33,2081,889],{"class":43},[33,2083,2084,2086,2089],{"class":35,"line":100},[33,2085,894],{"class":43},[33,2087,2088],{"class":86},"EditProduct",[33,2090,900],{"class":43},[33,2092,2093,2096,2098,2100,2102,2104,2106,2108,2110,2112],{"class":35,"line":118},[33,2094,2095],{"class":93},"    action",[33,2097,679],{"class":43},[33,2099,1346],{"class":39},[33,2101,1349],{"class":43},[33,2103,1352],{"class":238},[33,2105,684],{"class":43},[33,2107,1357],{"class":238},[33,2109,507],{"class":43},[33,2111,510],{"class":39},[33,2113,1364],{"class":43},[33,2115,2116,2118,2121,2123,2125,2128,2131],{"class":35,"line":133},[33,2117,1379],{"class":39},[33,2119,2120],{"class":86}," formData",[33,2122,90],{"class":39},[33,2124,288],{"class":39},[33,2126,2127],{"class":43}," request.",[33,2129,2130],{"class":93},"formData",[33,2132,258],{"class":43},[33,2134,2135,2137,2139,2141,2143,2145,2147,2149,2151,2153,2155,2157],{"class":35,"line":152},[33,2136,1379],{"class":39},[33,2138,1382],{"class":86},[33,2140,90],{"class":39},[33,2142,288],{"class":39},[33,2144,1389],{"class":93},[33,2146,312],{"class":43},[33,2148,699],{"class":50},[33,2150,1352],{"class":43},[33,2152,321],{"class":50},[33,2154,647],{"class":43},[33,2156,327],{"class":50},[33,2158,1404],{"class":43},[33,2160,2161,2164,2167],{"class":35,"line":158},[33,2162,2163],{"class":43},"        method: ",[33,2165,2166],{"class":50},"'PUT'",[33,2168,889],{"class":43},[33,2170,2171,2174,2177,2179,2182,2185,2188],{"class":35,"line":163},[33,2172,2173],{"class":43},"        body: ",[33,2175,2176],{"class":86},"JSON",[33,2178,321],{"class":43},[33,2180,2181],{"class":93},"stringify",[33,2183,2184],{"class":43},"(Object.",[33,2186,2187],{"class":93},"fromEntries",[33,2189,2190],{"class":43},"(formData)),\n",[33,2192,2193,2196,2199,2201,2204],{"class":35,"line":175},[33,2194,2195],{"class":43},"        headers: { ",[33,2197,2198],{"class":50},"'Content-Type'",[33,2200,679],{"class":43},[33,2202,2203],{"class":50},"'application\u002Fjson'",[33,2205,2206],{"class":43}," },\n",[33,2208,2209],{"class":35,"line":195},[33,2210,1414],{"class":43},[33,2212,2213,2215,2217,2219],{"class":35,"line":373},[33,2214,1419],{"class":39},[33,2216,1477],{"class":43},[33,2218,1480],{"class":39},[33,2220,2221],{"class":43},"res.ok) {\n",[33,2223,2224],{"class":35,"line":384},[33,2225,2226],{"class":64},"        \u002F\u002F Return errors as data (not throw) to show inline validation errors.\n",[33,2228,2229,2232,2235,2238,2240,2242],{"class":35,"line":419},[33,2230,2231],{"class":39},"        return",[33,2233,2234],{"class":43}," { errors: ",[33,2236,2237],{"class":39},"await",[33,2239,1510],{"class":43},[33,2241,1513],{"class":93},[33,2243,2244],{"class":43},"() }\n",[33,2246,2247],{"class":35,"line":439},[33,2248,1470],{"class":43},[33,2250,2251,2253,2256,2258,2261],{"class":35,"line":449},[33,2252,1507],{"class":39},[33,2254,2255],{"class":43}," { success: ",[33,2257,333],{"class":86},[33,2259,2260],{"class":43}," }  ",[33,2262,2263],{"class":64},"\u002F\u002F triggers loader revalidation automatically\n",[33,2265,2266],{"class":35,"line":455},[33,2267,1520],{"class":43},[33,2269,2270],{"class":35,"line":460},[33,2271,1064],{"class":43},[33,2273,2274],{"class":35,"line":465},[33,2275,155],{"class":43},[33,2277,2278],{"class":35,"line":471},[33,2279,58],{"emptyLinePlaceholder":57},[33,2281,2282,2284,2287],{"class":35,"line":477},[33,2283,166],{"class":39},[33,2285,2286],{"class":93}," EditProduct",[33,2288,172],{"class":43},[33,2290,2291,2293,2295,2297,2299],{"class":35,"line":531},[33,2292,247],{"class":39},[33,2294,1547],{"class":86},[33,2296,90],{"class":39},[33,2298,1552],{"class":93},[33,2300,258],{"class":43},[33,2302,2303,2305,2308,2310,2313,2315],{"class":35,"line":537},[33,2304,247],{"class":39},[33,2306,2307],{"class":86}," actionData",[33,2309,90],{"class":39},[33,2311,2312],{"class":93}," useActionData",[33,2314,1555],{"class":43},[33,2316,2317],{"class":64},"\u002F\u002F whatever the action returned\n",[33,2319,2320,2322,2325,2327,2330,2332],{"class":35,"line":1100},[33,2321,247],{"class":39},[33,2323,2324],{"class":86}," navigation",[33,2326,90],{"class":39},[33,2328,2329],{"class":93}," useNavigation",[33,2331,1555],{"class":43},[33,2333,2334],{"class":64},"\u002F\u002F { state: 'idle' | 'submitting' | 'loading' }\n",[33,2336,2337],{"class":35,"line":1110},[33,2338,58],{"emptyLinePlaceholder":57},[33,2340,2341,2343],{"class":35,"line":1117},[33,2342,178],{"class":39},[33,2344,358],{"class":43},[33,2346,2347,2349,2352,2355,2357,2360],{"class":35,"line":1126},[33,2348,363],{"class":43},[33,2350,2351],{"class":86},"Form",[33,2353,2354],{"class":93}," method",[33,2356,189],{"class":39},[33,2358,2359],{"class":50},"\"post\"",[33,2361,370],{"class":43},[33,2363,2364,2366,2369,2372,2374,2377,2380,2382],{"class":35,"line":1136},[33,2365,387],{"class":43},[33,2367,2368],{"class":366},"input",[33,2370,2371],{"class":93}," name",[33,2373,189],{"class":39},[33,2375,2376],{"class":50},"\"name\"",[33,2378,2379],{"class":93}," defaultValue",[33,2381,189],{"class":39},[33,2383,2384],{"class":43},"{product.name} \u002F>\n",[33,2386,2387,2390,2393,2395,2397,2399,2401,2404,2407,2409],{"class":35,"line":1159},[33,2388,2389],{"class":43},"      {actionData?.errors?.name ",[33,2391,2392],{"class":39},"&&",[33,2394,181],{"class":43},[33,2396,1591],{"class":366},[33,2398,496],{"class":93},[33,2400,189],{"class":39},[33,2402,2403],{"class":50},"\"error\"",[33,2405,2406],{"class":43},">{actionData.errors.name}\u003C\u002F",[33,2408,1591],{"class":366},[33,2410,2411],{"class":43},">}\n",[33,2413,2414,2416,2418,2421,2423,2426,2429,2432],{"class":35,"line":1180},[33,2415,387],{"class":43},[33,2417,424],{"class":366},[33,2419,2420],{"class":93}," disabled",[33,2422,189],{"class":39},[33,2424,2425],{"class":43},"{navigation.state ",[33,2427,2428],{"class":39},"!==",[33,2430,2431],{"class":50}," 'idle'",[33,2433,528],{"class":43},[33,2435,2436,2439,2441,2444,2447,2450,2452,2455],{"class":35,"line":1201},[33,2437,2438],{"class":43},"        {navigation.state ",[33,2440,717],{"class":39},[33,2442,2443],{"class":50}," 'submitting'",[33,2445,2446],{"class":39}," ?",[33,2448,2449],{"class":50}," 'Saving…'",[33,2451,522],{"class":39},[33,2453,2454],{"class":50}," 'Save'",[33,2456,198],{"class":43},[33,2458,2459,2461,2463],{"class":35,"line":1211},[33,2460,1204],{"class":43},[33,2462,424],{"class":366},[33,2464,370],{"class":43},[33,2466,2467,2469,2471],{"class":35,"line":1221},[33,2468,442],{"class":43},[33,2470,2351],{"class":86},[33,2472,370],{"class":43},[33,2474,2475],{"class":35,"line":1242},[33,2476,452],{"class":43},[33,2478,2479],{"class":35,"line":1252},[33,2480,198],{"class":43},[14,2482,2484],{"id":2483},"error-boundaries-per-route","Error Boundaries Per Route",[19,2486,2488],{"filename":2487,"language":22},"error_boundaries.js",[24,2489,2491],{"className":26,"code":2490,"language":22,"meta":28,"style":28},"import { useRouteError, isRouteErrorResponse, Link } from 'react-router-dom'\n\nconst router = createBrowserRouter([\n  {\n    path: '\u002F',\n    element: \u003CRootLayout \u002F>,\n    errorElement: \u003CRootError \u002F>,      \u002F\u002F catches errors from layout + all children\n    children: [\n      { path: 'products\u002F:productId', element: \u003CProductDetail \u002F>, loader: productLoader,\n        errorElement: \u003CProductError \u002F>,  \u002F\u002F scoped — only catches this route's errors\n      },\n    ],\n  },\n])\n\nfunction ProductError() {\n  const error = useRouteError()\n\n  if (isRouteErrorResponse(error)) {\n    \u002F\u002F Thrown Response objects have .status, .statusText, .data\n    if (error.status === 404) return \u003Cp>Product not found. \u003CLink to=\"\u002Fproducts\">Browse all\u003C\u002FLink>\u003C\u002Fp>\n    if (error.status === 401) return \u003Cp>Please log in.\u003C\u002Fp>\n    return \u003Cp>{error.status}: {error.statusText}\u003C\u002Fp>\n  }\n  \u002F\u002F A plain Error thrown during render or in a loader — no .status.\n  if (error instanceof Error) return \u003Cp>Unexpected: {error.message}\u003C\u002Fp>\n  return \u003Cp>Unknown error.\u003C\u002Fp>\n}\n",[30,2492,2493,2504,2508,2520,2524,2532,2540,2552,2556,2569,2582,2586,2590,2594,2598,2602,2611,2625,2629,2641,2646,2688,2714,2730,2734,2739,2767,2782],{"__ignoreMap":28},[33,2494,2495,2497,2500,2502],{"class":35,"line":36},[33,2496,40],{"class":39},[33,2498,2499],{"class":43}," { useRouteError, isRouteErrorResponse, Link } ",[33,2501,47],{"class":39},[33,2503,51],{"class":50},[33,2505,2506],{"class":35,"line":54},[33,2507,58],{"emptyLinePlaceholder":57},[33,2509,2510,2512,2514,2516,2518],{"class":35,"line":61},[33,2511,83],{"class":39},[33,2513,87],{"class":86},[33,2515,90],{"class":39},[33,2517,94],{"class":93},[33,2519,97],{"class":43},[33,2521,2522],{"class":35,"line":68},[33,2523,879],{"class":43},[33,2525,2526,2528,2530],{"class":35,"line":74},[33,2527,884],{"class":43},[33,2529,106],{"class":50},[33,2531,889],{"class":43},[33,2533,2534,2536,2538],{"class":35,"line":80},[33,2535,894],{"class":43},[33,2537,897],{"class":86},[33,2539,900],{"class":43},[33,2541,2542,2544,2546,2549],{"class":35,"line":100},[33,2543,905],{"class":43},[33,2545,908],{"class":86},[33,2547,2548],{"class":43}," \u002F>,      ",[33,2550,2551],{"class":64},"\u002F\u002F catches errors from layout + all children\n",[33,2553,2554],{"class":35,"line":118},[33,2555,919],{"class":43},[33,2557,2558,2560,2562,2564,2566],{"class":35,"line":133},[33,2559,941],{"class":43},[33,2561,958],{"class":50},[33,2563,109],{"class":43},[33,2565,580],{"class":86},[33,2567,2568],{"class":43}," \u002F>, loader: productLoader,\n",[33,2570,2571,2574,2577,2579],{"class":35,"line":152},[33,2572,2573],{"class":43},"        errorElement: \u003C",[33,2575,2576],{"class":86},"ProductError",[33,2578,911],{"class":43},[33,2580,2581],{"class":64},"\u002F\u002F scoped — only catches this route's errors\n",[33,2583,2584],{"class":35,"line":158},[33,2585,1054],{"class":43},[33,2587,2588],{"class":35,"line":163},[33,2589,1059],{"class":43},[33,2591,2592],{"class":35,"line":175},[33,2593,1064],{"class":43},[33,2595,2596],{"class":35,"line":195},[33,2597,155],{"class":43},[33,2599,2600],{"class":35,"line":373},[33,2601,58],{"emptyLinePlaceholder":57},[33,2603,2604,2606,2609],{"class":35,"line":384},[33,2605,166],{"class":39},[33,2607,2608],{"class":93}," ProductError",[33,2610,172],{"class":43},[33,2612,2613,2615,2618,2620,2623],{"class":35,"line":419},[33,2614,247],{"class":39},[33,2616,2617],{"class":86}," error",[33,2619,90],{"class":39},[33,2621,2622],{"class":93}," useRouteError",[33,2624,258],{"class":43},[33,2626,2627],{"class":35,"line":439},[33,2628,58],{"emptyLinePlaceholder":57},[33,2630,2631,2633,2635,2638],{"class":35,"line":449},[33,2632,711],{"class":39},[33,2634,1477],{"class":43},[33,2636,2637],{"class":93},"isRouteErrorResponse",[33,2639,2640],{"class":43},"(error)) {\n",[33,2642,2643],{"class":35,"line":455},[33,2644,2645],{"class":64},"    \u002F\u002F Thrown Response objects have .status, .statusText, .data\n",[33,2647,2648,2650,2653,2655,2657,2659,2661,2663,2665,2668,2670,2672,2674,2676,2679,2681,2684,2686],{"class":35,"line":460},[33,2649,1904],{"class":39},[33,2651,2652],{"class":43}," (error.status ",[33,2654,717],{"class":39},[33,2656,1427],{"class":86},[33,2658,723],{"class":43},[33,2660,726],{"class":39},[33,2662,181],{"class":43},[33,2664,1591],{"class":366},[33,2666,2667],{"class":43},">Product not found. \u003C",[33,2669,390],{"class":86},[33,2671,393],{"class":93},[33,2673,189],{"class":39},[33,2675,1170],{"class":50},[33,2677,2678],{"class":43},">Browse all\u003C\u002F",[33,2680,390],{"class":86},[33,2682,2683],{"class":43},">\u003C\u002F",[33,2685,1591],{"class":366},[33,2687,370],{"class":43},[33,2689,2690,2692,2694,2696,2699,2701,2703,2705,2707,2710,2712],{"class":35,"line":465},[33,2691,1904],{"class":39},[33,2693,2652],{"class":43},[33,2695,717],{"class":39},[33,2697,2698],{"class":86}," 401",[33,2700,723],{"class":43},[33,2702,726],{"class":39},[33,2704,181],{"class":43},[33,2706,1591],{"class":366},[33,2708,2709],{"class":43},">Please log in.\u003C\u002F",[33,2711,1591],{"class":366},[33,2713,370],{"class":43},[33,2715,2716,2719,2721,2723,2726,2728],{"class":35,"line":471},[33,2717,2718],{"class":39},"    return",[33,2720,181],{"class":43},[33,2722,1591],{"class":366},[33,2724,2725],{"class":43},">{error.status}: {error.statusText}\u003C\u002F",[33,2727,1591],{"class":366},[33,2729,370],{"class":43},[33,2731,2732],{"class":35,"line":477},[33,2733,347],{"class":43},[33,2735,2736],{"class":35,"line":531},[33,2737,2738],{"class":64},"  \u002F\u002F A plain Error thrown during render or in a loader — no .status.\n",[33,2740,2741,2743,2746,2749,2752,2754,2756,2758,2760,2763,2765],{"class":35,"line":537},[33,2742,711],{"class":39},[33,2744,2745],{"class":43}," (error ",[33,2747,2748],{"class":39},"instanceof",[33,2750,2751],{"class":93}," Error",[33,2753,723],{"class":43},[33,2755,726],{"class":39},[33,2757,181],{"class":43},[33,2759,1591],{"class":366},[33,2761,2762],{"class":43},">Unexpected: {error.message}\u003C\u002F",[33,2764,1591],{"class":366},[33,2766,370],{"class":43},[33,2768,2769,2771,2773,2775,2778,2780],{"class":35,"line":1100},[33,2770,178],{"class":39},[33,2772,181],{"class":43},[33,2774,1591],{"class":366},[33,2776,2777],{"class":43},">Unknown error.\u003C\u002F",[33,2779,1591],{"class":366},[33,2781,370],{"class":43},[33,2783,2784],{"class":35,"line":1110},[33,2785,198],{"class":43},[14,2787,2789],{"id":2788},"protected-routes","Protected Routes",[19,2791,2793],{"filename":2792,"language":22},"protected_routes.js",[24,2794,2796],{"className":26,"code":2795,"language":22,"meta":28,"style":28},"import { Navigate, Outlet, useLocation } from 'react-router-dom'\n\n\u002F\u002F THREE-STATE auth check — loading, authenticated, unauthenticated.\n\u002F\u002F Two-state (user | null) causes a flash redirect for logged-in users\n\u002F\u002F because `null` means \"haven't checked yet\" AND \"confirmed logged out.\"\nfunction RequireAuth() {\n  const { user, status } = useAuth()\n  const location = useLocation()\n\n  if (status === 'loading') return \u003CFullScreenSpinner \u002F>\n  if (status === 'unauthenticated') {\n    \u002F\u002F `replace` overwrites history so back-button doesn't re-trigger the redirect.\n    \u002F\u002F `state.from` lets LoginPage redirect back after successful login.\n    return \u003CNavigate to=\"\u002Flogin\" state={{ from: location }} replace \u002F>\n  }\n  return \u003COutlet \u002F>  \u002F\u002F user is confirmed authenticated — render children\n}\n\nfunction RequireRole({ role }) {\n  const { user } = useAuth()\n  if (!user.roles.includes(role)) return \u003CNavigate to=\"\u002Fforbidden\" replace \u002F>\n  return \u003COutlet \u002F>\n}\n\nconst router = createBrowserRouter([\n  {\n    element: \u003CRequireAuth \u002F>,\n    children: [\n      { path: '\u002Fdashboard', element: \u003CDashboard \u002F> },\n      {\n        element: \u003CRequireRole role=\"admin\" \u002F>,\n        children: [\n          { path: '\u002Fadmin', element: \u003CAdminPanel \u002F> },\n          { path: '\u002Fadmin\u002Fusers', element: \u003CUserManagement \u002F> },\n        ],\n      },\n    ],\n  },\n  { path: '\u002Flogin', element: \u003CLoginPage \u002F> },\n])\n\nfunction LoginPage() {\n  const navigate = useNavigate()\n  const location = useLocation()\n  const from = location.state?.from?.pathname || '\u002Fdashboard'\n\n  async function handleLogin(credentials) {\n    await login(credentials)\n    navigate(from, { replace: true })  \u002F\u002F go back to where the user was trying to go\n  }\n  return \u003CLoginForm onSubmit={handleLogin} \u002F>\n}\n",[30,2797,2798,2809,2813,2818,2823,2828,2837,2858,2872,2876,2897,2910,2915,2920,2949,2953,2967,2971,2975,2989,3005,3040,3050,3054,3058,3070,3074,3083,3087,3101,3105,3122,3126,3140,3154,3158,3162,3166,3170,3184,3188,3193,3203,3216,3229,3247,3252,3269,3281,3297,3302,3320],{"__ignoreMap":28},[33,2799,2800,2802,2805,2807],{"class":35,"line":36},[33,2801,40],{"class":39},[33,2803,2804],{"class":43}," { Navigate, Outlet, useLocation } ",[33,2806,47],{"class":39},[33,2808,51],{"class":50},[33,2810,2811],{"class":35,"line":54},[33,2812,58],{"emptyLinePlaceholder":57},[33,2814,2815],{"class":35,"line":61},[33,2816,2817],{"class":64},"\u002F\u002F THREE-STATE auth check — loading, authenticated, unauthenticated.\n",[33,2819,2820],{"class":35,"line":68},[33,2821,2822],{"class":64},"\u002F\u002F Two-state (user | null) causes a flash redirect for logged-in users\n",[33,2824,2825],{"class":35,"line":74},[33,2826,2827],{"class":64},"\u002F\u002F because `null` means \"haven't checked yet\" AND \"confirmed logged out.\"\n",[33,2829,2830,2832,2835],{"class":35,"line":80},[33,2831,166],{"class":39},[33,2833,2834],{"class":93}," RequireAuth",[33,2836,172],{"class":43},[33,2838,2839,2841,2843,2845,2847,2849,2851,2853,2856],{"class":35,"line":100},[33,2840,247],{"class":39},[33,2842,644],{"class":43},[33,2844,1661],{"class":86},[33,2846,684],{"class":43},[33,2848,687],{"class":86},[33,2850,650],{"class":43},[33,2852,189],{"class":39},[33,2854,2855],{"class":93}," useAuth",[33,2857,258],{"class":43},[33,2859,2860,2862,2865,2867,2870],{"class":35,"line":118},[33,2861,247],{"class":39},[33,2863,2864],{"class":86}," location",[33,2866,90],{"class":39},[33,2868,2869],{"class":93}," useLocation",[33,2871,258],{"class":43},[33,2873,2874],{"class":35,"line":133},[33,2875,58],{"emptyLinePlaceholder":57},[33,2877,2878,2880,2882,2884,2886,2888,2890,2892,2895],{"class":35,"line":152},[33,2879,711],{"class":39},[33,2881,714],{"class":43},[33,2883,717],{"class":39},[33,2885,720],{"class":50},[33,2887,723],{"class":43},[33,2889,726],{"class":39},[33,2891,181],{"class":43},[33,2893,2894],{"class":86},"FullScreenSpinner",[33,2896,734],{"class":43},[33,2898,2899,2901,2903,2905,2908],{"class":35,"line":158},[33,2900,711],{"class":39},[33,2902,714],{"class":43},[33,2904,717],{"class":39},[33,2906,2907],{"class":50}," 'unauthenticated'",[33,2909,1430],{"class":43},[33,2911,2912],{"class":35,"line":163},[33,2913,2914],{"class":64},"    \u002F\u002F `replace` overwrites history so back-button doesn't re-trigger the redirect.\n",[33,2916,2917],{"class":35,"line":175},[33,2918,2919],{"class":64},"    \u002F\u002F `state.from` lets LoginPage redirect back after successful login.\n",[33,2921,2922,2924,2926,2929,2931,2933,2936,2939,2941,2944,2947],{"class":35,"line":195},[33,2923,2718],{"class":39},[33,2925,181],{"class":43},[33,2927,2928],{"class":86},"Navigate",[33,2930,393],{"class":93},[33,2932,189],{"class":39},[33,2934,2935],{"class":50},"\"\u002Flogin\"",[33,2937,2938],{"class":93}," state",[33,2940,189],{"class":39},[33,2942,2943],{"class":43},"{{ from: location }} ",[33,2945,2946],{"class":93},"replace",[33,2948,734],{"class":43},[33,2950,2951],{"class":35,"line":373},[33,2952,347],{"class":43},[33,2954,2955,2957,2959,2961,2964],{"class":35,"line":384},[33,2956,178],{"class":39},[33,2958,181],{"class":43},[33,2960,1232],{"class":86},[33,2962,2963],{"class":43}," \u002F>  ",[33,2965,2966],{"class":64},"\u002F\u002F user is confirmed authenticated — render children\n",[33,2968,2969],{"class":35,"line":419},[33,2970,198],{"class":43},[33,2972,2973],{"class":35,"line":439},[33,2974,58],{"emptyLinePlaceholder":57},[33,2976,2977,2979,2982,2984,2987],{"class":35,"line":449},[33,2978,166],{"class":39},[33,2980,2981],{"class":93}," RequireRole",[33,2983,235],{"class":43},[33,2985,2986],{"class":238},"role",[33,2988,242],{"class":43},[33,2990,2991,2993,2995,2997,2999,3001,3003],{"class":35,"line":455},[33,2992,247],{"class":39},[33,2994,644],{"class":43},[33,2996,1661],{"class":86},[33,2998,650],{"class":43},[33,3000,189],{"class":39},[33,3002,2855],{"class":93},[33,3004,258],{"class":43},[33,3006,3007,3009,3011,3013,3016,3019,3022,3024,3026,3028,3030,3032,3035,3038],{"class":35,"line":460},[33,3008,711],{"class":39},[33,3010,1477],{"class":43},[33,3012,1480],{"class":39},[33,3014,3015],{"class":43},"user.roles.",[33,3017,3018],{"class":93},"includes",[33,3020,3021],{"class":43},"(role)) ",[33,3023,726],{"class":39},[33,3025,181],{"class":43},[33,3027,2928],{"class":86},[33,3029,393],{"class":93},[33,3031,189],{"class":39},[33,3033,3034],{"class":50},"\"\u002Fforbidden\"",[33,3036,3037],{"class":93}," replace",[33,3039,734],{"class":43},[33,3041,3042,3044,3046,3048],{"class":35,"line":465},[33,3043,178],{"class":39},[33,3045,181],{"class":43},[33,3047,1232],{"class":86},[33,3049,734],{"class":43},[33,3051,3052],{"class":35,"line":471},[33,3053,198],{"class":43},[33,3055,3056],{"class":35,"line":477},[33,3057,58],{"emptyLinePlaceholder":57},[33,3059,3060,3062,3064,3066,3068],{"class":35,"line":531},[33,3061,83],{"class":39},[33,3063,87],{"class":86},[33,3065,90],{"class":39},[33,3067,94],{"class":93},[33,3069,97],{"class":43},[33,3071,3072],{"class":35,"line":537},[33,3073,879],{"class":43},[33,3075,3076,3078,3081],{"class":35,"line":1100},[33,3077,894],{"class":43},[33,3079,3080],{"class":86},"RequireAuth",[33,3082,900],{"class":43},[33,3084,3085],{"class":35,"line":1110},[33,3086,919],{"class":43},[33,3088,3089,3091,3094,3096,3099],{"class":35,"line":1117},[33,3090,941],{"class":43},[33,3092,3093],{"class":50},"'\u002Fdashboard'",[33,3095,109],{"class":43},[33,3097,3098],{"class":86},"Dashboard",[33,3100,115],{"class":43},[33,3102,3103],{"class":35,"line":1126},[33,3104,969],{"class":43},[33,3106,3107,3109,3112,3115,3117,3120],{"class":35,"line":1136},[33,3108,984],{"class":43},[33,3110,3111],{"class":86},"RequireRole",[33,3113,3114],{"class":93}," role",[33,3116,189],{"class":39},[33,3118,3119],{"class":50},"\"admin\"",[33,3121,900],{"class":43},[33,3123,3124],{"class":35,"line":1159},[33,3125,998],{"class":43},[33,3127,3128,3130,3133,3135,3138],{"class":35,"line":1180},[33,3129,1020],{"class":43},[33,3131,3132],{"class":50},"'\u002Fadmin'",[33,3134,109],{"class":43},[33,3136,3137],{"class":86},"AdminPanel",[33,3139,115],{"class":43},[33,3141,3142,3144,3147,3149,3152],{"class":35,"line":1201},[33,3143,1020],{"class":43},[33,3145,3146],{"class":50},"'\u002Fadmin\u002Fusers'",[33,3148,109],{"class":43},[33,3150,3151],{"class":86},"UserManagement",[33,3153,115],{"class":43},[33,3155,3156],{"class":35,"line":1211},[33,3157,1049],{"class":43},[33,3159,3160],{"class":35,"line":1221},[33,3161,1054],{"class":43},[33,3163,3164],{"class":35,"line":1242},[33,3165,1059],{"class":43},[33,3167,3168],{"class":35,"line":1252},[33,3169,1064],{"class":43},[33,3171,3172,3174,3177,3179,3182],{"class":35,"line":1261},[33,3173,103],{"class":43},[33,3175,3176],{"class":50},"'\u002Flogin'",[33,3178,109],{"class":43},[33,3180,3181],{"class":86},"LoginPage",[33,3183,115],{"class":43},[33,3185,3186],{"class":35,"line":1266},[33,3187,155],{"class":43},[33,3189,3191],{"class":35,"line":3190},41,[33,3192,58],{"emptyLinePlaceholder":57},[33,3194,3196,3198,3201],{"class":35,"line":3195},42,[33,3197,166],{"class":39},[33,3199,3200],{"class":93}," LoginPage",[33,3202,172],{"class":43},[33,3204,3206,3208,3210,3212,3214],{"class":35,"line":3205},43,[33,3207,247],{"class":39},[33,3209,250],{"class":86},[33,3211,90],{"class":39},[33,3213,255],{"class":93},[33,3215,258],{"class":43},[33,3217,3219,3221,3223,3225,3227],{"class":35,"line":3218},44,[33,3220,247],{"class":39},[33,3222,2864],{"class":86},[33,3224,90],{"class":39},[33,3226,2869],{"class":93},[33,3228,258],{"class":43},[33,3230,3232,3234,3237,3239,3242,3244],{"class":35,"line":3231},45,[33,3233,247],{"class":39},[33,3235,3236],{"class":86}," from",[33,3238,90],{"class":39},[33,3240,3241],{"class":43}," location.state?.from?.pathname ",[33,3243,826],{"class":39},[33,3245,3246],{"class":50}," '\u002Fdashboard'\n",[33,3248,3250],{"class":35,"line":3249},46,[33,3251,58],{"emptyLinePlaceholder":57},[33,3253,3255,3257,3259,3262,3264,3267],{"class":35,"line":3254},47,[33,3256,267],{"class":39},[33,3258,270],{"class":39},[33,3260,3261],{"class":93}," handleLogin",[33,3263,312],{"class":43},[33,3265,3266],{"class":238},"credentials",[33,3268,1430],{"class":43},[33,3270,3272,3275,3278],{"class":35,"line":3271},48,[33,3273,3274],{"class":39},"    await",[33,3276,3277],{"class":93}," login",[33,3279,3280],{"class":43},"(credentials)\n",[33,3282,3284,3286,3289,3291,3294],{"class":35,"line":3283},49,[33,3285,309],{"class":93},[33,3287,3288],{"class":43},"(from, { replace: ",[33,3290,333],{"class":86},[33,3292,3293],{"class":43}," })  ",[33,3295,3296],{"class":64},"\u002F\u002F go back to where the user was trying to go\n",[33,3298,3300],{"class":35,"line":3299},50,[33,3301,347],{"class":43},[33,3303,3305,3307,3309,3312,3315,3317],{"class":35,"line":3304},51,[33,3306,178],{"class":39},[33,3308,181],{"class":43},[33,3310,3311],{"class":86},"LoginForm",[33,3313,3314],{"class":93}," onSubmit",[33,3316,189],{"class":39},[33,3318,3319],{"class":43},"{handleLogin} \u002F>\n",[33,3321,3323],{"class":35,"line":3322},52,[33,3324,198],{"class":43},[14,3326,3328],{"id":3327},"lazy-loading-routes-with-suspense","Lazy Loading Routes with Suspense",[19,3330,3332],{"filename":3331,"language":22},"lazy_routes.js",[24,3333,3335],{"className":26,"code":3334,"language":22,"meta":28,"style":28},"import { lazy, Suspense } from 'react'\nimport { createBrowserRouter, RouterProvider } from 'react-router-dom'\n\n\u002F\u002F Each lazy() call creates a separate JS chunk — users only download\n\u002F\u002F the code for routes they actually visit.\nconst HomePage = lazy(() => import('.\u002Fpages\u002FHomePage'))\nconst ProductsPage = lazy(() => import('.\u002Fpages\u002FProductsPage'))\nconst ProductDetail = lazy(() => import('.\u002Fpages\u002FProductDetail'))\nconst SettingsPage = lazy(() => import('.\u002Fpages\u002FSettingsPage'))\nconst AdminPanel = lazy(() => import('.\u002Fpages\u002FAdminPanel'))\n\nconst PageSpinner = () => \u003Cdiv className=\"spinner\">Loading…\u003C\u002Fdiv>\n\nconst router = createBrowserRouter([\n  {\n    path: '\u002F',\n    element: \u003CSuspense fallback={\u003CPageSpinner \u002F>}>\u003CHomePage \u002F>\u003C\u002FSuspense>,\n  },\n  {\n    path: '\u002Fproducts',\n    element: \u003CSuspense fallback={\u003CPageSpinner \u002F>}>\u003CProductsPage \u002F>\u003C\u002FSuspense>,\n  },\n  {\n    path: '\u002Fproducts\u002F:productId',\n    element: \u003CSuspense fallback={\u003CPageSpinner \u002F>}>\u003CProductDetail \u002F>\u003C\u002FSuspense>,\n    loader: productLoader,  \u002F\u002F loader runs in parallel with chunk download\n  },\n  {\n    path: '\u002Fsettings',\n    element: \u003CSuspense fallback={\u003CPageSpinner \u002F>}>\u003CSettingsPage \u002F>\u003C\u002FSuspense>,\n  },\n  \u002F\u002F Admin chunk is never downloaded by non-admin users at all.\n  {\n    path: '\u002Fadmin',\n    element: \u003CSuspense fallback={\u003CPageSpinner \u002F>}>\u003CAdminPanel \u002F>\u003C\u002FSuspense>,\n  },\n])\n\nfunction App() {\n  return \u003CRouterProvider router={router} \u002F>\n}\n",[30,3336,3337,3349,3359,3363,3368,3373,3401,3425,3448,3472,3496,3500,3532,3536,3548,3552,3560,3590,3594,3598,3607,3631,3635,3639,3647,3671,3679,3683,3687,3696,3721,3725,3730,3734,3742,3766,3770,3774,3778,3786,3800],{"__ignoreMap":28},[33,3338,3339,3341,3344,3346],{"class":35,"line":36},[33,3340,40],{"class":39},[33,3342,3343],{"class":43}," { lazy, Suspense } ",[33,3345,47],{"class":39},[33,3347,3348],{"class":50}," 'react'\n",[33,3350,3351,3353,3355,3357],{"class":35,"line":54},[33,3352,40],{"class":39},[33,3354,44],{"class":43},[33,3356,47],{"class":39},[33,3358,51],{"class":50},[33,3360,3361],{"class":35,"line":61},[33,3362,58],{"emptyLinePlaceholder":57},[33,3364,3365],{"class":35,"line":68},[33,3366,3367],{"class":64},"\u002F\u002F Each lazy() call creates a separate JS chunk — users only download\n",[33,3369,3370],{"class":35,"line":74},[33,3371,3372],{"class":64},"\u002F\u002F the code for routes they actually visit.\n",[33,3374,3375,3377,3380,3382,3385,3388,3390,3393,3395,3398],{"class":35,"line":80},[33,3376,83],{"class":39},[33,3378,3379],{"class":86}," HomePage",[33,3381,90],{"class":39},[33,3383,3384],{"class":93}," lazy",[33,3386,3387],{"class":43},"(() ",[33,3389,510],{"class":39},[33,3391,3392],{"class":39}," import",[33,3394,312],{"class":43},[33,3396,3397],{"class":50},"'.\u002Fpages\u002FHomePage'",[33,3399,3400],{"class":43},"))\n",[33,3402,3403,3405,3408,3410,3412,3414,3416,3418,3420,3423],{"class":35,"line":100},[33,3404,83],{"class":39},[33,3406,3407],{"class":86}," ProductsPage",[33,3409,90],{"class":39},[33,3411,3384],{"class":93},[33,3413,3387],{"class":43},[33,3415,510],{"class":39},[33,3417,3392],{"class":39},[33,3419,312],{"class":43},[33,3421,3422],{"class":50},"'.\u002Fpages\u002FProductsPage'",[33,3424,3400],{"class":43},[33,3426,3427,3429,3431,3433,3435,3437,3439,3441,3443,3446],{"class":35,"line":118},[33,3428,83],{"class":39},[33,3430,635],{"class":86},[33,3432,90],{"class":39},[33,3434,3384],{"class":93},[33,3436,3387],{"class":43},[33,3438,510],{"class":39},[33,3440,3392],{"class":39},[33,3442,312],{"class":43},[33,3444,3445],{"class":50},"'.\u002Fpages\u002FProductDetail'",[33,3447,3400],{"class":43},[33,3449,3450,3452,3455,3457,3459,3461,3463,3465,3467,3470],{"class":35,"line":133},[33,3451,83],{"class":39},[33,3453,3454],{"class":86}," SettingsPage",[33,3456,90],{"class":39},[33,3458,3384],{"class":93},[33,3460,3387],{"class":43},[33,3462,510],{"class":39},[33,3464,3392],{"class":39},[33,3466,312],{"class":43},[33,3468,3469],{"class":50},"'.\u002Fpages\u002FSettingsPage'",[33,3471,3400],{"class":43},[33,3473,3474,3476,3479,3481,3483,3485,3487,3489,3491,3494],{"class":35,"line":152},[33,3475,83],{"class":39},[33,3477,3478],{"class":86}," AdminPanel",[33,3480,90],{"class":39},[33,3482,3384],{"class":93},[33,3484,3387],{"class":43},[33,3486,510],{"class":39},[33,3488,3392],{"class":39},[33,3490,312],{"class":43},[33,3492,3493],{"class":50},"'.\u002Fpages\u002FAdminPanel'",[33,3495,3400],{"class":43},[33,3497,3498],{"class":35,"line":158},[33,3499,58],{"emptyLinePlaceholder":57},[33,3501,3502,3504,3507,3509,3512,3514,3516,3518,3520,3522,3525,3528,3530],{"class":35,"line":163},[33,3503,83],{"class":39},[33,3505,3506],{"class":93}," PageSpinner",[33,3508,90],{"class":39},[33,3510,3511],{"class":43}," () ",[33,3513,510],{"class":39},[33,3515,181],{"class":43},[33,3517,367],{"class":366},[33,3519,496],{"class":93},[33,3521,189],{"class":39},[33,3523,3524],{"class":50},"\"spinner\"",[33,3526,3527],{"class":43},">Loading…\u003C\u002F",[33,3529,367],{"class":366},[33,3531,370],{"class":43},[33,3533,3534],{"class":35,"line":175},[33,3535,58],{"emptyLinePlaceholder":57},[33,3537,3538,3540,3542,3544,3546],{"class":35,"line":195},[33,3539,83],{"class":39},[33,3541,87],{"class":86},[33,3543,90],{"class":39},[33,3545,94],{"class":93},[33,3547,97],{"class":43},[33,3549,3550],{"class":35,"line":373},[33,3551,879],{"class":43},[33,3553,3554,3556,3558],{"class":35,"line":384},[33,3555,884],{"class":43},[33,3557,106],{"class":50},[33,3559,889],{"class":43},[33,3561,3562,3564,3567,3570,3572,3575,3578,3581,3583,3585,3587],{"class":35,"line":419},[33,3563,894],{"class":43},[33,3565,3566],{"class":86},"Suspense",[33,3568,3569],{"class":93}," fallback",[33,3571,189],{"class":39},[33,3573,3574],{"class":43},"{\u003C",[33,3576,3577],{"class":86},"PageSpinner",[33,3579,3580],{"class":43}," \u002F>}>\u003C",[33,3582,112],{"class":86},[33,3584,1235],{"class":43},[33,3586,3566],{"class":86},[33,3588,3589],{"class":43},">,\n",[33,3591,3592],{"class":35,"line":439},[33,3593,1064],{"class":43},[33,3595,3596],{"class":35,"line":449},[33,3597,879],{"class":43},[33,3599,3600,3602,3605],{"class":35,"line":455},[33,3601,884],{"class":43},[33,3603,3604],{"class":50},"'\u002Fproducts'",[33,3606,889],{"class":43},[33,3608,3609,3611,3613,3615,3617,3619,3621,3623,3625,3627,3629],{"class":35,"line":460},[33,3610,894],{"class":43},[33,3612,3566],{"class":86},[33,3614,3569],{"class":93},[33,3616,189],{"class":39},[33,3618,3574],{"class":43},[33,3620,3577],{"class":86},[33,3622,3580],{"class":43},[33,3624,949],{"class":86},[33,3626,1235],{"class":43},[33,3628,3566],{"class":86},[33,3630,3589],{"class":43},[33,3632,3633],{"class":35,"line":465},[33,3634,1064],{"class":43},[33,3636,3637],{"class":35,"line":471},[33,3638,879],{"class":43},[33,3640,3641,3643,3645],{"class":35,"line":477},[33,3642,884],{"class":43},[33,3644,575],{"class":50},[33,3646,889],{"class":43},[33,3648,3649,3651,3653,3655,3657,3659,3661,3663,3665,3667,3669],{"class":35,"line":531},[33,3650,894],{"class":43},[33,3652,3566],{"class":86},[33,3654,3569],{"class":93},[33,3656,189],{"class":39},[33,3658,3574],{"class":43},[33,3660,3577],{"class":86},[33,3662,3580],{"class":43},[33,3664,580],{"class":86},[33,3666,1235],{"class":43},[33,3668,3566],{"class":86},[33,3670,3589],{"class":43},[33,3672,3673,3676],{"class":35,"line":537},[33,3674,3675],{"class":43},"    loader: productLoader,  ",[33,3677,3678],{"class":64},"\u002F\u002F loader runs in parallel with chunk download\n",[33,3680,3681],{"class":35,"line":1100},[33,3682,1064],{"class":43},[33,3684,3685],{"class":35,"line":1110},[33,3686,879],{"class":43},[33,3688,3689,3691,3694],{"class":35,"line":1117},[33,3690,884],{"class":43},[33,3692,3693],{"class":50},"'\u002Fsettings'",[33,3695,889],{"class":43},[33,3697,3698,3700,3702,3704,3706,3708,3710,3712,3715,3717,3719],{"class":35,"line":1126},[33,3699,894],{"class":43},[33,3701,3566],{"class":86},[33,3703,3569],{"class":93},[33,3705,189],{"class":39},[33,3707,3574],{"class":43},[33,3709,3577],{"class":86},[33,3711,3580],{"class":43},[33,3713,3714],{"class":86},"SettingsPage",[33,3716,1235],{"class":43},[33,3718,3566],{"class":86},[33,3720,3589],{"class":43},[33,3722,3723],{"class":35,"line":1136},[33,3724,1064],{"class":43},[33,3726,3727],{"class":35,"line":1159},[33,3728,3729],{"class":64},"  \u002F\u002F Admin chunk is never downloaded by non-admin users at all.\n",[33,3731,3732],{"class":35,"line":1180},[33,3733,879],{"class":43},[33,3735,3736,3738,3740],{"class":35,"line":1201},[33,3737,884],{"class":43},[33,3739,3132],{"class":50},[33,3741,889],{"class":43},[33,3743,3744,3746,3748,3750,3752,3754,3756,3758,3760,3762,3764],{"class":35,"line":1211},[33,3745,894],{"class":43},[33,3747,3566],{"class":86},[33,3749,3569],{"class":93},[33,3751,189],{"class":39},[33,3753,3574],{"class":43},[33,3755,3577],{"class":86},[33,3757,3580],{"class":43},[33,3759,3137],{"class":86},[33,3761,1235],{"class":43},[33,3763,3566],{"class":86},[33,3765,3589],{"class":43},[33,3767,3768],{"class":35,"line":1221},[33,3769,1064],{"class":43},[33,3771,3772],{"class":35,"line":1242},[33,3773,155],{"class":43},[33,3775,3776],{"class":35,"line":1252},[33,3777,58],{"emptyLinePlaceholder":57},[33,3779,3780,3782,3784],{"class":35,"line":1261},[33,3781,166],{"class":39},[33,3783,169],{"class":93},[33,3785,172],{"class":43},[33,3787,3788,3790,3792,3794,3796,3798],{"class":35,"line":1266},[33,3789,178],{"class":39},[33,3791,181],{"class":43},[33,3793,184],{"class":86},[33,3795,87],{"class":93},[33,3797,189],{"class":39},[33,3799,192],{"class":43},[33,3801,3802],{"class":35,"line":3190},[33,3803,198],{"class":43},[1616,3805,3807],{"id":3806},"lazy-routes-with-shared-suspense-boundary","Lazy Routes with Shared Suspense Boundary",[19,3809,3811],{"filename":3810,"language":22},"lazy_shared_suspense.js",[24,3812,3814],{"className":26,"code":3813,"language":22,"meta":28,"style":28},"\u002F\u002F One Suspense boundary wrapping a layout + all its lazy children —\n\u002F\u002F the fallback shows for the FIRST lazy child to load, then children\n\u002F\u002F swap in as their chunks arrive. Simpler than per-route Suspense.\nconst router = createBrowserRouter([\n  {\n    path: '\u002F',\n    element: (\n      \u003CRootLayout>\n        \u003CSuspense fallback={\u003CPageSpinner \u002F>}>\n          \u003COutlet \u002F>\n        \u003C\u002FSuspense>\n      \u003C\u002FRootLayout>\n    ),\n    children: [\n      { index: true, element: \u003CHomePage \u002F> },\n      { path: 'products', element: \u003CProductsPage \u002F> },\n      { path: 'settings', element: \u003CSettingsPage \u002F> },\n    ],\n  },\n])\n\n\u002F\u002F ANTI-PATTERN: wrapping \u003COutlet> in Suspense inside a layout that ALSO\n\u002F\u002F has non-lazy content — the spinner replaces the entire outlet area,\n\u002F\u002F including any persisted layout state, on every lazy route load.\n",[30,3815,3816,3821,3826,3831,3843,3847,3855,3863,3871,3888,3897,3906,3914,3919,3923,3935,3947,3959,3963,3967,3971,3975,3980,3985],{"__ignoreMap":28},[33,3817,3818],{"class":35,"line":36},[33,3819,3820],{"class":64},"\u002F\u002F One Suspense boundary wrapping a layout + all its lazy children —\n",[33,3822,3823],{"class":35,"line":54},[33,3824,3825],{"class":64},"\u002F\u002F the fallback shows for the FIRST lazy child to load, then children\n",[33,3827,3828],{"class":35,"line":61},[33,3829,3830],{"class":64},"\u002F\u002F swap in as their chunks arrive. Simpler than per-route Suspense.\n",[33,3832,3833,3835,3837,3839,3841],{"class":35,"line":68},[33,3834,83],{"class":39},[33,3836,87],{"class":86},[33,3838,90],{"class":39},[33,3840,94],{"class":93},[33,3842,97],{"class":43},[33,3844,3845],{"class":35,"line":74},[33,3846,879],{"class":43},[33,3848,3849,3851,3853],{"class":35,"line":80},[33,3850,884],{"class":43},[33,3852,106],{"class":50},[33,3854,889],{"class":43},[33,3856,3857,3860],{"class":35,"line":100},[33,3858,3859],{"class":93},"    element",[33,3861,3862],{"class":43},": (\n",[33,3864,3865,3867,3869],{"class":35,"line":118},[33,3866,387],{"class":43},[33,3868,897],{"class":86},[33,3870,370],{"class":43},[33,3872,3873,3875,3877,3879,3881,3883,3885],{"class":35,"line":133},[33,3874,1139],{"class":43},[33,3876,3566],{"class":86},[33,3878,3569],{"class":93},[33,3880,189],{"class":39},[33,3882,3574],{"class":43},[33,3884,3577],{"class":86},[33,3886,3887],{"class":43}," \u002F>}>\n",[33,3889,3890,3893,3895],{"class":35,"line":152},[33,3891,3892],{"class":43},"          \u003C",[33,3894,1232],{"class":86},[33,3896,734],{"class":43},[33,3898,3899,3902,3904],{"class":35,"line":158},[33,3900,3901],{"class":43},"        \u003C\u002F",[33,3903,3566],{"class":86},[33,3905,370],{"class":43},[33,3907,3908,3910,3912],{"class":35,"line":163},[33,3909,1204],{"class":43},[33,3911,897],{"class":86},[33,3913,370],{"class":43},[33,3915,3916],{"class":35,"line":175},[33,3917,3918],{"class":43},"    ),\n",[33,3920,3921],{"class":35,"line":195},[33,3922,919],{"class":43},[33,3924,3925,3927,3929,3931,3933],{"class":35,"line":373},[33,3926,924],{"class":43},[33,3928,333],{"class":86},[33,3930,109],{"class":43},[33,3932,112],{"class":86},[33,3934,115],{"class":43},[33,3936,3937,3939,3941,3943,3945],{"class":35,"line":384},[33,3938,941],{"class":43},[33,3940,944],{"class":50},[33,3942,109],{"class":43},[33,3944,949],{"class":86},[33,3946,115],{"class":43},[33,3948,3949,3951,3953,3955,3957],{"class":35,"line":419},[33,3950,941],{"class":43},[33,3952,1037],{"class":50},[33,3954,109],{"class":43},[33,3956,3714],{"class":86},[33,3958,115],{"class":43},[33,3960,3961],{"class":35,"line":439},[33,3962,1059],{"class":43},[33,3964,3965],{"class":35,"line":449},[33,3966,1064],{"class":43},[33,3968,3969],{"class":35,"line":455},[33,3970,155],{"class":43},[33,3972,3973],{"class":35,"line":460},[33,3974,58],{"emptyLinePlaceholder":57},[33,3976,3977],{"class":35,"line":465},[33,3978,3979],{"class":64},"\u002F\u002F ANTI-PATTERN: wrapping \u003COutlet> in Suspense inside a layout that ALSO\n",[33,3981,3982],{"class":35,"line":471},[33,3983,3984],{"class":64},"\u002F\u002F has non-lazy content — the spinner replaces the entire outlet area,\n",[33,3986,3987],{"class":35,"line":477},[33,3988,3989],{"class":64},"\u002F\u002F including any persisted layout state, on every lazy route load.\n",[14,3991,3993],{"id":3992},"search-params-usesearchparams","Search Params — useSearchParams",[19,3995,3997],{"filename":3996,"language":22},"search_params.js",[24,3998,4000],{"className":26,"code":3999,"language":22,"meta":28,"style":28},"import { useSearchParams } from 'react-router-dom'\n\n\u002F\u002F Mirrors useState's tuple API but syncs to the URL query string.\n\u002F\u002F State in the URL = shareable, bookmarkable, refresh-surviving.\nfunction ProductList() {\n  const [searchParams, setSearchParams] = useSearchParams()\n  const sort = searchParams.get('sort') || 'relevance'\n  const page = Number(searchParams.get('page')) || 1\n  const filter = searchParams.get('filter')  \u002F\u002F null if not present\n\n  function handleSortChange(newSort) {\n    setSearchParams(prev => {\n      const next = new URLSearchParams(prev)\n      next.set('sort', newSort)\n      next.set('page', '1')  \u002F\u002F reset pagination when sort changes\n      return next\n    })\n  }\n\n  function handleFilterChange(key, value) {\n    setSearchParams(prev => {\n      const next = new URLSearchParams(prev)\n      if (value) next.set(key, value)\n      else next.delete(key)  \u002F\u002F remove param when filter cleared\n      return next\n    })\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Cselect value={sort} onChange={e => handleSortChange(e.target.value)}>\n        \u003Coption value=\"relevance\">Relevance\u003C\u002Foption>\n        \u003Coption value=\"price\">Price\u003C\u002Foption>\n      \u003C\u002Fselect>\n      \u003CProductGrid sort={sort} page={page} filter={filter} \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F ANTI-PATTERN: calling setSearchParams on every keystroke without debouncing —\n\u002F\u002F floods browser history with one entry per character. Use a local useState\n\u002F\u002F for the input + debounce into setSearchParams (see Ch 11 useDebounce).\nfunction SearchInput() {\n  const [searchParams, setSearchParams] = useSearchParams()\n  const [localQuery, setLocalQuery] = useState(searchParams.get('q') || '')\n  const debouncedQuery = useDebounce(localQuery, 300)\n\n  useEffect(() => {\n    setSearchParams(prev => {\n      const next = new URLSearchParams(prev)\n      if (debouncedQuery) next.set('q', debouncedQuery)\n      else next.delete('q')\n      return next\n    }, { replace: true })  \u002F\u002F replace: don't spam history entries\n  }, [debouncedQuery])\n\n  return \u003Cinput value={localQuery} onChange={e => setLocalQuery(e.target.value)} \u002F>\n}\n",[30,4001,4002,4013,4017,4022,4027,4036,4059,4085,4115,4139,4143,4158,4172,4189,4203,4223,4230,4235,4239,4243,4261,4273,4287,4299,4316,4322,4326,4330,4334,4340,4348,4380,4401,4421,4429,4458,4466,4470,4474,4478,4483,4488,4493,4502,4522,4560,4580,4584,4595,4607,4621,4637,4651,4658,4671,4677,4682,4714],{"__ignoreMap":28},[33,4003,4004,4006,4009,4011],{"class":35,"line":36},[33,4005,40],{"class":39},[33,4007,4008],{"class":43}," { useSearchParams } ",[33,4010,47],{"class":39},[33,4012,51],{"class":50},[33,4014,4015],{"class":35,"line":54},[33,4016,58],{"emptyLinePlaceholder":57},[33,4018,4019],{"class":35,"line":61},[33,4020,4021],{"class":64},"\u002F\u002F Mirrors useState's tuple API but syncs to the URL query string.\n",[33,4023,4024],{"class":35,"line":68},[33,4025,4026],{"class":64},"\u002F\u002F State in the URL = shareable, bookmarkable, refresh-surviving.\n",[33,4028,4029,4031,4034],{"class":35,"line":74},[33,4030,166],{"class":39},[33,4032,4033],{"class":93}," ProductList",[33,4035,172],{"class":43},[33,4037,4038,4040,4042,4045,4047,4050,4052,4054,4057],{"class":35,"line":80},[33,4039,247],{"class":39},[33,4041,1658],{"class":43},[33,4043,4044],{"class":86},"searchParams",[33,4046,684],{"class":43},[33,4048,4049],{"class":86},"setSearchParams",[33,4051,823],{"class":43},[33,4053,189],{"class":39},[33,4055,4056],{"class":93}," useSearchParams",[33,4058,258],{"class":43},[33,4060,4061,4063,4066,4068,4071,4073,4075,4078,4080,4082],{"class":35,"line":100},[33,4062,247],{"class":39},[33,4064,4065],{"class":86}," sort",[33,4067,90],{"class":39},[33,4069,4070],{"class":43}," searchParams.",[33,4072,1896],{"class":93},[33,4074,312],{"class":43},[33,4076,4077],{"class":50},"'sort'",[33,4079,723],{"class":43},[33,4081,826],{"class":39},[33,4083,4084],{"class":50}," 'relevance'\n",[33,4086,4087,4089,4092,4094,4097,4100,4102,4104,4107,4110,4112],{"class":35,"line":118},[33,4088,247],{"class":39},[33,4090,4091],{"class":86}," page",[33,4093,90],{"class":39},[33,4095,4096],{"class":93}," Number",[33,4098,4099],{"class":43},"(searchParams.",[33,4101,1896],{"class":93},[33,4103,312],{"class":43},[33,4105,4106],{"class":50},"'page'",[33,4108,4109],{"class":43},")) ",[33,4111,826],{"class":39},[33,4113,4114],{"class":86}," 1\n",[33,4116,4117,4119,4122,4124,4126,4128,4130,4133,4136],{"class":35,"line":133},[33,4118,247],{"class":39},[33,4120,4121],{"class":86}," filter",[33,4123,90],{"class":39},[33,4125,4070],{"class":43},[33,4127,1896],{"class":93},[33,4129,312],{"class":43},[33,4131,4132],{"class":50},"'filter'",[33,4134,4135],{"class":43},")  ",[33,4137,4138],{"class":64},"\u002F\u002F null if not present\n",[33,4140,4141],{"class":35,"line":152},[33,4142,58],{"emptyLinePlaceholder":57},[33,4144,4145,4148,4151,4153,4156],{"class":35,"line":158},[33,4146,4147],{"class":39},"  function",[33,4149,4150],{"class":93}," handleSortChange",[33,4152,312],{"class":43},[33,4154,4155],{"class":238},"newSort",[33,4157,1430],{"class":43},[33,4159,4160,4163,4165,4168,4170],{"class":35,"line":163},[33,4161,4162],{"class":93},"    setSearchParams",[33,4164,312],{"class":43},[33,4166,4167],{"class":238},"prev",[33,4169,1711],{"class":39},[33,4171,1364],{"class":43},[33,4173,4174,4176,4179,4181,4183,4186],{"class":35,"line":175},[33,4175,1379],{"class":39},[33,4177,4178],{"class":86}," next",[33,4180,90],{"class":39},[33,4182,1448],{"class":39},[33,4184,4185],{"class":93}," URLSearchParams",[33,4187,4188],{"class":43},"(prev)\n",[33,4190,4191,4194,4196,4198,4200],{"class":35,"line":195},[33,4192,4193],{"class":43},"      next.",[33,4195,2011],{"class":93},[33,4197,312],{"class":43},[33,4199,4077],{"class":50},[33,4201,4202],{"class":43},", newSort)\n",[33,4204,4205,4207,4209,4211,4213,4215,4218,4220],{"class":35,"line":373},[33,4206,4193],{"class":43},[33,4208,2011],{"class":93},[33,4210,312],{"class":43},[33,4212,4106],{"class":50},[33,4214,684],{"class":43},[33,4216,4217],{"class":50},"'1'",[33,4219,4135],{"class":43},[33,4221,4222],{"class":64},"\u002F\u002F reset pagination when sort changes\n",[33,4224,4225,4227],{"class":35,"line":384},[33,4226,1507],{"class":39},[33,4228,4229],{"class":43}," next\n",[33,4231,4232],{"class":35,"line":419},[33,4233,4234],{"class":43},"    })\n",[33,4236,4237],{"class":35,"line":439},[33,4238,347],{"class":43},[33,4240,4241],{"class":35,"line":449},[33,4242,58],{"emptyLinePlaceholder":57},[33,4244,4245,4247,4250,4252,4254,4256,4259],{"class":35,"line":455},[33,4246,4147],{"class":39},[33,4248,4249],{"class":93}," handleFilterChange",[33,4251,312],{"class":43},[33,4253,1957],{"class":238},[33,4255,684],{"class":43},[33,4257,4258],{"class":238},"value",[33,4260,1430],{"class":43},[33,4262,4263,4265,4267,4269,4271],{"class":35,"line":460},[33,4264,4162],{"class":93},[33,4266,312],{"class":43},[33,4268,4167],{"class":238},[33,4270,1711],{"class":39},[33,4272,1364],{"class":43},[33,4274,4275,4277,4279,4281,4283,4285],{"class":35,"line":465},[33,4276,1379],{"class":39},[33,4278,4178],{"class":86},[33,4280,90],{"class":39},[33,4282,1448],{"class":39},[33,4284,4185],{"class":93},[33,4286,4188],{"class":43},[33,4288,4289,4291,4294,4296],{"class":35,"line":471},[33,4290,1419],{"class":39},[33,4292,4293],{"class":43}," (value) next.",[33,4295,2011],{"class":93},[33,4297,4298],{"class":43},"(key, value)\n",[33,4300,4301,4304,4307,4310,4313],{"class":35,"line":477},[33,4302,4303],{"class":39},"      else",[33,4305,4306],{"class":43}," next.",[33,4308,4309],{"class":93},"delete",[33,4311,4312],{"class":43},"(key)  ",[33,4314,4315],{"class":64},"\u002F\u002F remove param when filter cleared\n",[33,4317,4318,4320],{"class":35,"line":531},[33,4319,1507],{"class":39},[33,4321,4229],{"class":43},[33,4323,4324],{"class":35,"line":537},[33,4325,4234],{"class":43},[33,4327,4328],{"class":35,"line":1100},[33,4329,347],{"class":43},[33,4331,4332],{"class":35,"line":1110},[33,4333,58],{"emptyLinePlaceholder":57},[33,4335,4336,4338],{"class":35,"line":1117},[33,4337,178],{"class":39},[33,4339,358],{"class":43},[33,4341,4342,4344,4346],{"class":35,"line":1126},[33,4343,363],{"class":43},[33,4345,367],{"class":366},[33,4347,370],{"class":43},[33,4349,4350,4352,4355,4358,4360,4363,4366,4368,4370,4373,4375,4377],{"class":35,"line":1136},[33,4351,387],{"class":43},[33,4353,4354],{"class":366},"select",[33,4356,4357],{"class":93}," value",[33,4359,189],{"class":39},[33,4361,4362],{"class":43},"{sort} ",[33,4364,4365],{"class":93},"onChange",[33,4367,189],{"class":39},[33,4369,398],{"class":43},[33,4371,4372],{"class":238},"e",[33,4374,1711],{"class":39},[33,4376,4150],{"class":93},[33,4378,4379],{"class":43},"(e.target.value)}>\n",[33,4381,4382,4384,4387,4389,4391,4394,4397,4399],{"class":35,"line":1159},[33,4383,1139],{"class":43},[33,4385,4386],{"class":366},"option",[33,4388,4357],{"class":93},[33,4390,189],{"class":39},[33,4392,4393],{"class":50},"\"relevance\"",[33,4395,4396],{"class":43},">Relevance\u003C\u002F",[33,4398,4386],{"class":366},[33,4400,370],{"class":43},[33,4402,4403,4405,4407,4409,4411,4414,4417,4419],{"class":35,"line":1180},[33,4404,1139],{"class":43},[33,4406,4386],{"class":366},[33,4408,4357],{"class":93},[33,4410,189],{"class":39},[33,4412,4413],{"class":50},"\"price\"",[33,4415,4416],{"class":43},">Price\u003C\u002F",[33,4418,4386],{"class":366},[33,4420,370],{"class":43},[33,4422,4423,4425,4427],{"class":35,"line":1201},[33,4424,1204],{"class":43},[33,4426,4354],{"class":366},[33,4428,370],{"class":43},[33,4430,4431,4433,4436,4438,4440,4442,4445,4447,4450,4453,4455],{"class":35,"line":1211},[33,4432,387],{"class":43},[33,4434,4435],{"class":86},"ProductGrid",[33,4437,4065],{"class":93},[33,4439,189],{"class":39},[33,4441,4362],{"class":43},[33,4443,4444],{"class":93},"page",[33,4446,189],{"class":39},[33,4448,4449],{"class":43},"{page} ",[33,4451,4452],{"class":93},"filter",[33,4454,189],{"class":39},[33,4456,4457],{"class":43},"{filter} \u002F>\n",[33,4459,4460,4462,4464],{"class":35,"line":1221},[33,4461,442],{"class":43},[33,4463,367],{"class":366},[33,4465,370],{"class":43},[33,4467,4468],{"class":35,"line":1242},[33,4469,452],{"class":43},[33,4471,4472],{"class":35,"line":1252},[33,4473,198],{"class":43},[33,4475,4476],{"class":35,"line":1261},[33,4477,58],{"emptyLinePlaceholder":57},[33,4479,4480],{"class":35,"line":1266},[33,4481,4482],{"class":64},"\u002F\u002F ANTI-PATTERN: calling setSearchParams on every keystroke without debouncing —\n",[33,4484,4485],{"class":35,"line":3190},[33,4486,4487],{"class":64},"\u002F\u002F floods browser history with one entry per character. Use a local useState\n",[33,4489,4490],{"class":35,"line":3195},[33,4491,4492],{"class":64},"\u002F\u002F for the input + debounce into setSearchParams (see Ch 11 useDebounce).\n",[33,4494,4495,4497,4500],{"class":35,"line":3205},[33,4496,166],{"class":39},[33,4498,4499],{"class":93}," SearchInput",[33,4501,172],{"class":43},[33,4503,4504,4506,4508,4510,4512,4514,4516,4518,4520],{"class":35,"line":3218},[33,4505,247],{"class":39},[33,4507,1658],{"class":43},[33,4509,4044],{"class":86},[33,4511,684],{"class":43},[33,4513,4049],{"class":86},[33,4515,823],{"class":43},[33,4517,189],{"class":39},[33,4519,4056],{"class":93},[33,4521,258],{"class":43},[33,4523,4524,4526,4528,4531,4533,4536,4538,4540,4543,4545,4547,4549,4552,4554,4556,4558],{"class":35,"line":3231},[33,4525,247],{"class":39},[33,4527,1658],{"class":43},[33,4529,4530],{"class":86},"localQuery",[33,4532,684],{"class":43},[33,4534,4535],{"class":86},"setLocalQuery",[33,4537,823],{"class":43},[33,4539,189],{"class":39},[33,4541,4542],{"class":93}," useState",[33,4544,4099],{"class":43},[33,4546,1896],{"class":93},[33,4548,312],{"class":43},[33,4550,4551],{"class":50},"'q'",[33,4553,723],{"class":43},[33,4555,826],{"class":39},[33,4557,525],{"class":50},[33,4559,706],{"class":43},[33,4561,4562,4564,4567,4569,4572,4575,4578],{"class":35,"line":3249},[33,4563,247],{"class":39},[33,4565,4566],{"class":86}," debouncedQuery",[33,4568,90],{"class":39},[33,4570,4571],{"class":93}," useDebounce",[33,4573,4574],{"class":43},"(localQuery, ",[33,4576,4577],{"class":86},"300",[33,4579,706],{"class":43},[33,4581,4582],{"class":35,"line":3254},[33,4583,58],{"emptyLinePlaceholder":57},[33,4585,4586,4589,4591,4593],{"class":35,"line":3271},[33,4587,4588],{"class":93},"  useEffect",[33,4590,3387],{"class":43},[33,4592,510],{"class":39},[33,4594,1364],{"class":43},[33,4596,4597,4599,4601,4603,4605],{"class":35,"line":3283},[33,4598,4162],{"class":93},[33,4600,312],{"class":43},[33,4602,4167],{"class":238},[33,4604,1711],{"class":39},[33,4606,1364],{"class":43},[33,4608,4609,4611,4613,4615,4617,4619],{"class":35,"line":3299},[33,4610,1379],{"class":39},[33,4612,4178],{"class":86},[33,4614,90],{"class":39},[33,4616,1448],{"class":39},[33,4618,4185],{"class":93},[33,4620,4188],{"class":43},[33,4622,4623,4625,4628,4630,4632,4634],{"class":35,"line":3304},[33,4624,1419],{"class":39},[33,4626,4627],{"class":43}," (debouncedQuery) next.",[33,4629,2011],{"class":93},[33,4631,312],{"class":43},[33,4633,4551],{"class":50},[33,4635,4636],{"class":43},", debouncedQuery)\n",[33,4638,4639,4641,4643,4645,4647,4649],{"class":35,"line":3322},[33,4640,4303],{"class":39},[33,4642,4306],{"class":43},[33,4644,4309],{"class":93},[33,4646,312],{"class":43},[33,4648,4551],{"class":50},[33,4650,706],{"class":43},[33,4652,4654,4656],{"class":35,"line":4653},53,[33,4655,1507],{"class":39},[33,4657,4229],{"class":43},[33,4659,4661,4664,4666,4668],{"class":35,"line":4660},54,[33,4662,4663],{"class":43},"    }, { replace: ",[33,4665,333],{"class":86},[33,4667,3293],{"class":43},[33,4669,4670],{"class":64},"\u002F\u002F replace: don't spam history entries\n",[33,4672,4674],{"class":35,"line":4673},55,[33,4675,4676],{"class":43},"  }, [debouncedQuery])\n",[33,4678,4680],{"class":35,"line":4679},56,[33,4681,58],{"emptyLinePlaceholder":57},[33,4683,4685,4687,4689,4691,4693,4695,4698,4700,4702,4704,4706,4708,4711],{"class":35,"line":4684},57,[33,4686,178],{"class":39},[33,4688,181],{"class":43},[33,4690,2368],{"class":366},[33,4692,4357],{"class":93},[33,4694,189],{"class":39},[33,4696,4697],{"class":43},"{localQuery} ",[33,4699,4365],{"class":93},[33,4701,189],{"class":39},[33,4703,398],{"class":43},[33,4705,4372],{"class":238},[33,4707,1711],{"class":39},[33,4709,4710],{"class":93}," setLocalQuery",[33,4712,4713],{"class":43},"(e.target.value)} \u002F>\n",[33,4715,4717],{"class":35,"line":4716},58,[33,4718,198],{"class":43},[14,4720,4722],{"id":4721},"complex-implementation-full-app-router","Complex Implementation — Full App Router",[19,4724,4726],{"filename":4725,"language":22},"full_router.js",[24,4727,4729],{"className":26,"code":4728,"language":22,"meta":28,"style":28},"import {\n  createBrowserRouter, RouterProvider, Outlet, Link, NavLink,\n  useLoaderData, useActionData, useNavigation, useRouteError,\n  isRouteErrorResponse, Navigate, useLocation, useSearchParams,\n} from 'react-router-dom'\nimport { lazy, Suspense } from 'react'\n\n\u002F\u002F --- Lazy page chunks ---\nconst HomePage = lazy(() => import('.\u002Fpages\u002FHome'))\nconst ProductList = lazy(() => import('.\u002Fpages\u002FProductList'))\nconst ProductDetail = lazy(() => import('.\u002Fpages\u002FProductDetail'))\nconst Checkout = lazy(() => import('.\u002Fpages\u002FCheckout'))\nconst AdminDashboard = lazy(() => import('.\u002Fpages\u002FAdminDashboard'))\n\nconst Spinner = () => \u003Cdiv className=\"spinner\" \u002F>\n\n\u002F\u002F --- Loaders ---\nasync function productsLoader({ request }) {\n  const res = await fetch('\u002Fapi\u002Fproducts', { signal: request.signal })\n  if (!res.ok) throw new Response('Failed to load products', { status: res.status })\n  return res.json()\n}\n\nasync function productDetailLoader({ params, request }) {\n  const res = await fetch(`\u002Fapi\u002Fproducts\u002F${params.productId}`, { signal: request.signal })\n  if (res.status === 404) throw new Response('Not found', { status: 404 })\n  if (!res.ok) throw new Response('Server error', { status: 500 })\n  return res.json()\n}\n\n\u002F\u002F --- Actions ---\nasync function checkoutAction({ request }) {\n  const formData = await request.formData()\n  const res = await fetch('\u002Fapi\u002Fcheckout', {\n    method: 'POST',\n    body: formData,\n  })\n  if (!res.ok) return { error: 'Checkout failed' }\n  const order = await res.json()\n  return { order }  \u002F\u002F component reads via useActionData, then navigates\n}\n\n\u002F\u002F --- Layouts ---\nfunction RootLayout() {\n  return (\n    \u003Cdiv>\n      \u003Cnav>\n        \u003CNavLink to=\"\u002F\" end>Home\u003C\u002FNavLink>\n        \u003CNavLink to=\"\u002Fproducts\">Products\u003C\u002FNavLink>\n      \u003C\u002Fnav>\n      \u003Cmain>\u003COutlet \u002F>\u003C\u002Fmain>\n    \u003C\u002Fdiv>\n  )\n}\n\nfunction AdminLayout() {\n  return (\n    \u003Cdiv>\n      \u003Caside>\u003CAdminSidebar \u002F>\u003C\u002Faside>\n      \u003Cmain>\u003COutlet \u002F>\u003C\u002Fmain>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F --- Auth Guards ---\nfunction RequireAuth() {\n  const { user, status } = useAuth()\n  const location = useLocation()\n  if (status === 'loading') return \u003CSpinner \u002F>\n  if (!user) return \u003CNavigate to=\"\u002Flogin\" state={{ from: location }} replace \u002F>\n  return \u003COutlet \u002F>\n}\n\nfunction RequireAdmin() {\n  const { user } = useAuth()\n  if (user?.role !== 'admin') return \u003CNavigate to=\"\u002Fforbidden\" replace \u002F>\n  return \u003COutlet \u002F>\n}\n\n\u002F\u002F --- Error Boundaries ---\nfunction RootError() {\n  const error = useRouteError()\n  if (isRouteErrorResponse(error)) {\n    if (error.status === 404) return \u003CNotFoundPage \u002F>\n    return \u003CServerErrorPage status={error.status} message={error.statusText} \u002F>\n  }\n  return \u003CUnexpectedErrorPage error={error} \u002F>\n}\n\n\u002F\u002F --- Full Router Config ---\nconst router = createBrowserRouter([\n  {\n    path: '\u002F',\n    element: \u003CRootLayout \u002F>,\n    errorElement: \u003CRootError \u002F>,\n    children: [\n      { index: true, element: \u003CSuspense fallback={\u003CSpinner \u002F>}>\u003CHomePage \u002F>\u003C\u002FSuspense> },\n      {\n        path: 'products',\n        element: \u003CSuspense fallback={\u003CSpinner \u002F>}>\u003CProductList \u002F>\u003C\u002FSuspense>,\n        loader: productsLoader,\n      },\n      {\n        path: 'products\u002F:productId',\n        element: \u003CSuspense fallback={\u003CSpinner \u002F>}>\u003CProductDetail \u002F>\u003C\u002FSuspense>,\n        loader: productDetailLoader,\n      },\n      {\n        path: 'checkout',\n        element: \u003CSuspense fallback={\u003CSpinner \u002F>}>\u003CCheckout \u002F>\u003C\u002FSuspense>,\n        action: checkoutAction,\n      },\n    ],\n  },\n  {\n    element: \u003CRequireAuth \u002F>,\n    children: [\n      {\n        element: \u003CRequireAdmin \u002F>,\n        children: [\n          {\n            path: '\u002Fadmin',\n            element: \u003CAdminLayout \u002F>,\n            children: [\n              { index: true, element: \u003CSuspense fallback={\u003CSpinner \u002F>}>\u003CAdminDashboard \u002F>\u003C\u002FSuspense> },\n            ],\n          },\n        ],\n      },\n    ],\n  },\n  { path: '\u002Flogin', element: \u003CLoginPage \u002F> },\n])\n\nexport default function App() {\n  return \u003CRouterProvider router={router} fallbackElement={\u003CSpinner \u002F>} \u002F>\n}\n",[30,4730,4731,4737,4742,4747,4752,4761,4771,4775,4780,4803,4826,4848,4872,4896,4900,4925,4929,4934,4949,4968,4991,5001,5005,5009,5028,5054,5083,5109,5119,5123,5127,5132,5147,5163,5182,5192,5197,5202,5223,5239,5249,5253,5257,5262,5270,5276,5284,5292,5312,5330,5338,5354,5362,5366,5370,5374,5383,5389,5397,5416,5433,5442,5447,5452,5457,5463,5472,5493,5506,5527,5561,5572,5577,5582,5592,5609,5640,5651,5656,5661,5667,5677,5690,5701,5723,5749,5754,5771,5776,5781,5787,5800,5805,5814,5823,5832,5837,5867,5872,5881,5907,5913,5918,5923,5932,5957,5963,5968,5973,5983,6009,6015,6020,6025,6030,6035,6044,6049,6054,6064,6069,6075,6085,6096,6102,6133,6139,6145,6150,6155,6160,6165,6178,6183,6188,6203,6231],{"__ignoreMap":28},[33,4732,4733,4735],{"class":35,"line":36},[33,4734,40],{"class":39},[33,4736,1364],{"class":43},[33,4738,4739],{"class":35,"line":54},[33,4740,4741],{"class":43},"  createBrowserRouter, RouterProvider, Outlet, Link, NavLink,\n",[33,4743,4744],{"class":35,"line":61},[33,4745,4746],{"class":43},"  useLoaderData, useActionData, useNavigation, useRouteError,\n",[33,4748,4749],{"class":35,"line":68},[33,4750,4751],{"class":43},"  isRouteErrorResponse, Navigate, useLocation, useSearchParams,\n",[33,4753,4754,4757,4759],{"class":35,"line":74},[33,4755,4756],{"class":43},"} ",[33,4758,47],{"class":39},[33,4760,51],{"class":50},[33,4762,4763,4765,4767,4769],{"class":35,"line":80},[33,4764,40],{"class":39},[33,4766,3343],{"class":43},[33,4768,47],{"class":39},[33,4770,3348],{"class":50},[33,4772,4773],{"class":35,"line":100},[33,4774,58],{"emptyLinePlaceholder":57},[33,4776,4777],{"class":35,"line":118},[33,4778,4779],{"class":64},"\u002F\u002F --- Lazy page chunks ---\n",[33,4781,4782,4784,4786,4788,4790,4792,4794,4796,4798,4801],{"class":35,"line":133},[33,4783,83],{"class":39},[33,4785,3379],{"class":86},[33,4787,90],{"class":39},[33,4789,3384],{"class":93},[33,4791,3387],{"class":43},[33,4793,510],{"class":39},[33,4795,3392],{"class":39},[33,4797,312],{"class":43},[33,4799,4800],{"class":50},"'.\u002Fpages\u002FHome'",[33,4802,3400],{"class":43},[33,4804,4805,4807,4809,4811,4813,4815,4817,4819,4821,4824],{"class":35,"line":152},[33,4806,83],{"class":39},[33,4808,4033],{"class":86},[33,4810,90],{"class":39},[33,4812,3384],{"class":93},[33,4814,3387],{"class":43},[33,4816,510],{"class":39},[33,4818,3392],{"class":39},[33,4820,312],{"class":43},[33,4822,4823],{"class":50},"'.\u002Fpages\u002FProductList'",[33,4825,3400],{"class":43},[33,4827,4828,4830,4832,4834,4836,4838,4840,4842,4844,4846],{"class":35,"line":158},[33,4829,83],{"class":39},[33,4831,635],{"class":86},[33,4833,90],{"class":39},[33,4835,3384],{"class":93},[33,4837,3387],{"class":43},[33,4839,510],{"class":39},[33,4841,3392],{"class":39},[33,4843,312],{"class":43},[33,4845,3445],{"class":50},[33,4847,3400],{"class":43},[33,4849,4850,4852,4855,4857,4859,4861,4863,4865,4867,4870],{"class":35,"line":163},[33,4851,83],{"class":39},[33,4853,4854],{"class":86}," Checkout",[33,4856,90],{"class":39},[33,4858,3384],{"class":93},[33,4860,3387],{"class":43},[33,4862,510],{"class":39},[33,4864,3392],{"class":39},[33,4866,312],{"class":43},[33,4868,4869],{"class":50},"'.\u002Fpages\u002FCheckout'",[33,4871,3400],{"class":43},[33,4873,4874,4876,4879,4881,4883,4885,4887,4889,4891,4894],{"class":35,"line":175},[33,4875,83],{"class":39},[33,4877,4878],{"class":86}," AdminDashboard",[33,4880,90],{"class":39},[33,4882,3384],{"class":93},[33,4884,3387],{"class":43},[33,4886,510],{"class":39},[33,4888,3392],{"class":39},[33,4890,312],{"class":43},[33,4892,4893],{"class":50},"'.\u002Fpages\u002FAdminDashboard'",[33,4895,3400],{"class":43},[33,4897,4898],{"class":35,"line":195},[33,4899,58],{"emptyLinePlaceholder":57},[33,4901,4902,4904,4907,4909,4911,4913,4915,4917,4919,4921,4923],{"class":35,"line":373},[33,4903,83],{"class":39},[33,4905,4906],{"class":93}," Spinner",[33,4908,90],{"class":39},[33,4910,3511],{"class":43},[33,4912,510],{"class":39},[33,4914,181],{"class":43},[33,4916,367],{"class":366},[33,4918,496],{"class":93},[33,4920,189],{"class":39},[33,4922,3524],{"class":50},[33,4924,734],{"class":43},[33,4926,4927],{"class":35,"line":384},[33,4928,58],{"emptyLinePlaceholder":57},[33,4930,4931],{"class":35,"line":419},[33,4932,4933],{"class":64},"\u002F\u002F --- Loaders ---\n",[33,4935,4936,4938,4940,4943,4945,4947],{"class":35,"line":439},[33,4937,1346],{"class":39},[33,4939,270],{"class":39},[33,4941,4942],{"class":93}," productsLoader",[33,4944,235],{"class":43},[33,4946,1357],{"class":238},[33,4948,242],{"class":43},[33,4950,4951,4953,4955,4957,4959,4961,4963,4966],{"class":35,"line":449},[33,4952,247],{"class":39},[33,4954,1382],{"class":86},[33,4956,90],{"class":39},[33,4958,288],{"class":39},[33,4960,1389],{"class":93},[33,4962,312],{"class":43},[33,4964,4965],{"class":50},"'\u002Fapi\u002Fproducts'",[33,4967,1962],{"class":43},[33,4969,4970,4972,4974,4976,4978,4980,4982,4984,4986,4989],{"class":35,"line":455},[33,4971,711],{"class":39},[33,4973,1477],{"class":43},[33,4975,1480],{"class":39},[33,4977,1483],{"class":43},[33,4979,1486],{"class":39},[33,4981,1448],{"class":39},[33,4983,1451],{"class":93},[33,4985,312],{"class":43},[33,4987,4988],{"class":50},"'Failed to load products'",[33,4990,1986],{"class":43},[33,4992,4993,4995,4997,4999],{"class":35,"line":460},[33,4994,178],{"class":39},[33,4996,1510],{"class":43},[33,4998,1513],{"class":93},[33,5000,258],{"class":43},[33,5002,5003],{"class":35,"line":465},[33,5004,198],{"class":43},[33,5006,5007],{"class":35,"line":471},[33,5008,58],{"emptyLinePlaceholder":57},[33,5010,5011,5013,5015,5018,5020,5022,5024,5026],{"class":35,"line":477},[33,5012,1346],{"class":39},[33,5014,270],{"class":39},[33,5016,5017],{"class":93}," productDetailLoader",[33,5019,235],{"class":43},[33,5021,1352],{"class":238},[33,5023,684],{"class":43},[33,5025,1357],{"class":238},[33,5027,242],{"class":43},[33,5029,5030,5032,5034,5036,5038,5040,5042,5044,5046,5048,5050,5052],{"class":35,"line":531},[33,5031,247],{"class":39},[33,5033,1382],{"class":86},[33,5035,90],{"class":39},[33,5037,288],{"class":39},[33,5039,1389],{"class":93},[33,5041,312],{"class":43},[33,5043,699],{"class":50},[33,5045,1352],{"class":43},[33,5047,321],{"class":50},[33,5049,647],{"class":43},[33,5051,327],{"class":50},[33,5053,1962],{"class":43},[33,5055,5056,5058,5060,5062,5064,5066,5068,5070,5072,5074,5077,5079,5081],{"class":35,"line":537},[33,5057,711],{"class":39},[33,5059,1422],{"class":43},[33,5061,717],{"class":39},[33,5063,1427],{"class":86},[33,5065,723],{"class":43},[33,5067,1486],{"class":39},[33,5069,1448],{"class":39},[33,5071,1451],{"class":93},[33,5073,312],{"class":43},[33,5075,5076],{"class":50},"'Not found'",[33,5078,1459],{"class":43},[33,5080,1462],{"class":86},[33,5082,1465],{"class":43},[33,5084,5085,5087,5089,5091,5093,5095,5097,5099,5101,5103,5105,5107],{"class":35,"line":1100},[33,5086,711],{"class":39},[33,5088,1477],{"class":43},[33,5090,1480],{"class":39},[33,5092,1483],{"class":43},[33,5094,1486],{"class":39},[33,5096,1448],{"class":39},[33,5098,1451],{"class":93},[33,5100,312],{"class":43},[33,5102,1495],{"class":50},[33,5104,1459],{"class":43},[33,5106,1500],{"class":86},[33,5108,1465],{"class":43},[33,5110,5111,5113,5115,5117],{"class":35,"line":1110},[33,5112,178],{"class":39},[33,5114,1510],{"class":43},[33,5116,1513],{"class":93},[33,5118,258],{"class":43},[33,5120,5121],{"class":35,"line":1117},[33,5122,198],{"class":43},[33,5124,5125],{"class":35,"line":1126},[33,5126,58],{"emptyLinePlaceholder":57},[33,5128,5129],{"class":35,"line":1136},[33,5130,5131],{"class":64},"\u002F\u002F --- Actions ---\n",[33,5133,5134,5136,5138,5141,5143,5145],{"class":35,"line":1159},[33,5135,1346],{"class":39},[33,5137,270],{"class":39},[33,5139,5140],{"class":93}," checkoutAction",[33,5142,235],{"class":43},[33,5144,1357],{"class":238},[33,5146,242],{"class":43},[33,5148,5149,5151,5153,5155,5157,5159,5161],{"class":35,"line":1180},[33,5150,247],{"class":39},[33,5152,2120],{"class":86},[33,5154,90],{"class":39},[33,5156,288],{"class":39},[33,5158,2127],{"class":43},[33,5160,2130],{"class":93},[33,5162,258],{"class":43},[33,5164,5165,5167,5169,5171,5173,5175,5177,5180],{"class":35,"line":1201},[33,5166,247],{"class":39},[33,5168,1382],{"class":86},[33,5170,90],{"class":39},[33,5172,288],{"class":39},[33,5174,1389],{"class":93},[33,5176,312],{"class":43},[33,5178,5179],{"class":50},"'\u002Fapi\u002Fcheckout'",[33,5181,1404],{"class":43},[33,5183,5184,5187,5190],{"class":35,"line":1211},[33,5185,5186],{"class":43},"    method: ",[33,5188,5189],{"class":50},"'POST'",[33,5191,889],{"class":43},[33,5193,5194],{"class":35,"line":1221},[33,5195,5196],{"class":43},"    body: formData,\n",[33,5198,5199],{"class":35,"line":1242},[33,5200,5201],{"class":43},"  })\n",[33,5203,5204,5206,5208,5210,5212,5214,5217,5220],{"class":35,"line":1252},[33,5205,711],{"class":39},[33,5207,1477],{"class":43},[33,5209,1480],{"class":39},[33,5211,1483],{"class":43},[33,5213,726],{"class":39},[33,5215,5216],{"class":43}," { error: ",[33,5218,5219],{"class":50},"'Checkout failed'",[33,5221,5222],{"class":43}," }\n",[33,5224,5225,5227,5229,5231,5233,5235,5237],{"class":35,"line":1261},[33,5226,247],{"class":39},[33,5228,283],{"class":86},[33,5230,90],{"class":39},[33,5232,288],{"class":39},[33,5234,1510],{"class":43},[33,5236,1513],{"class":93},[33,5238,258],{"class":43},[33,5240,5241,5243,5246],{"class":35,"line":1266},[33,5242,178],{"class":39},[33,5244,5245],{"class":43}," { order }  ",[33,5247,5248],{"class":64},"\u002F\u002F component reads via useActionData, then navigates\n",[33,5250,5251],{"class":35,"line":3190},[33,5252,198],{"class":43},[33,5254,5255],{"class":35,"line":3195},[33,5256,58],{"emptyLinePlaceholder":57},[33,5258,5259],{"class":35,"line":3205},[33,5260,5261],{"class":64},"\u002F\u002F --- Layouts ---\n",[33,5263,5264,5266,5268],{"class":35,"line":3218},[33,5265,166],{"class":39},[33,5267,1105],{"class":93},[33,5269,172],{"class":43},[33,5271,5272,5274],{"class":35,"line":3231},[33,5273,178],{"class":39},[33,5275,358],{"class":43},[33,5277,5278,5280,5282],{"class":35,"line":3249},[33,5279,363],{"class":43},[33,5281,367],{"class":366},[33,5283,370],{"class":43},[33,5285,5286,5288,5290],{"class":35,"line":3254},[33,5287,387],{"class":43},[33,5289,1131],{"class":366},[33,5291,370],{"class":43},[33,5293,5294,5296,5298,5300,5302,5304,5306,5308,5310],{"class":35,"line":3271},[33,5295,1139],{"class":43},[33,5297,483],{"class":86},[33,5299,393],{"class":93},[33,5301,189],{"class":39},[33,5303,490],{"class":50},[33,5305,493],{"class":93},[33,5307,1152],{"class":43},[33,5309,483],{"class":86},[33,5311,370],{"class":43},[33,5313,5314,5316,5318,5320,5322,5324,5326,5328],{"class":35,"line":3283},[33,5315,1139],{"class":43},[33,5317,483],{"class":86},[33,5319,393],{"class":93},[33,5321,189],{"class":39},[33,5323,1170],{"class":50},[33,5325,1173],{"class":43},[33,5327,483],{"class":86},[33,5329,370],{"class":43},[33,5331,5332,5334,5336],{"class":35,"line":3299},[33,5333,1204],{"class":43},[33,5335,1131],{"class":366},[33,5337,370],{"class":43},[33,5339,5340,5342,5344,5346,5348,5350,5352],{"class":35,"line":3304},[33,5341,387],{"class":43},[33,5343,1226],{"class":366},[33,5345,1229],{"class":43},[33,5347,1232],{"class":86},[33,5349,1235],{"class":43},[33,5351,1226],{"class":366},[33,5353,370],{"class":43},[33,5355,5356,5358,5360],{"class":35,"line":3322},[33,5357,442],{"class":43},[33,5359,367],{"class":366},[33,5361,370],{"class":43},[33,5363,5364],{"class":35,"line":4653},[33,5365,452],{"class":43},[33,5367,5368],{"class":35,"line":4660},[33,5369,198],{"class":43},[33,5371,5372],{"class":35,"line":4673},[33,5373,58],{"emptyLinePlaceholder":57},[33,5375,5376,5378,5381],{"class":35,"line":4679},[33,5377,166],{"class":39},[33,5379,5380],{"class":93}," AdminLayout",[33,5382,172],{"class":43},[33,5384,5385,5387],{"class":35,"line":4684},[33,5386,178],{"class":39},[33,5388,358],{"class":43},[33,5390,5391,5393,5395],{"class":35,"line":4716},[33,5392,363],{"class":43},[33,5394,367],{"class":366},[33,5396,370],{"class":43},[33,5398,5400,5402,5405,5407,5410,5412,5414],{"class":35,"line":5399},59,[33,5401,387],{"class":43},[33,5403,5404],{"class":366},"aside",[33,5406,1229],{"class":43},[33,5408,5409],{"class":86},"AdminSidebar",[33,5411,1235],{"class":43},[33,5413,5404],{"class":366},[33,5415,370],{"class":43},[33,5417,5419,5421,5423,5425,5427,5429,5431],{"class":35,"line":5418},60,[33,5420,387],{"class":43},[33,5422,1226],{"class":366},[33,5424,1229],{"class":43},[33,5426,1232],{"class":86},[33,5428,1235],{"class":43},[33,5430,1226],{"class":366},[33,5432,370],{"class":43},[33,5434,5436,5438,5440],{"class":35,"line":5435},61,[33,5437,442],{"class":43},[33,5439,367],{"class":366},[33,5441,370],{"class":43},[33,5443,5445],{"class":35,"line":5444},62,[33,5446,452],{"class":43},[33,5448,5450],{"class":35,"line":5449},63,[33,5451,198],{"class":43},[33,5453,5455],{"class":35,"line":5454},64,[33,5456,58],{"emptyLinePlaceholder":57},[33,5458,5460],{"class":35,"line":5459},65,[33,5461,5462],{"class":64},"\u002F\u002F --- Auth Guards ---\n",[33,5464,5466,5468,5470],{"class":35,"line":5465},66,[33,5467,166],{"class":39},[33,5469,2834],{"class":93},[33,5471,172],{"class":43},[33,5473,5475,5477,5479,5481,5483,5485,5487,5489,5491],{"class":35,"line":5474},67,[33,5476,247],{"class":39},[33,5478,644],{"class":43},[33,5480,1661],{"class":86},[33,5482,684],{"class":43},[33,5484,687],{"class":86},[33,5486,650],{"class":43},[33,5488,189],{"class":39},[33,5490,2855],{"class":93},[33,5492,258],{"class":43},[33,5494,5496,5498,5500,5502,5504],{"class":35,"line":5495},68,[33,5497,247],{"class":39},[33,5499,2864],{"class":86},[33,5501,90],{"class":39},[33,5503,2869],{"class":93},[33,5505,258],{"class":43},[33,5507,5509,5511,5513,5515,5517,5519,5521,5523,5525],{"class":35,"line":5508},69,[33,5510,711],{"class":39},[33,5512,714],{"class":43},[33,5514,717],{"class":39},[33,5516,720],{"class":50},[33,5518,723],{"class":43},[33,5520,726],{"class":39},[33,5522,181],{"class":43},[33,5524,731],{"class":86},[33,5526,734],{"class":43},[33,5528,5530,5532,5534,5536,5539,5541,5543,5545,5547,5549,5551,5553,5555,5557,5559],{"class":35,"line":5529},70,[33,5531,711],{"class":39},[33,5533,1477],{"class":43},[33,5535,1480],{"class":39},[33,5537,5538],{"class":43},"user) ",[33,5540,726],{"class":39},[33,5542,181],{"class":43},[33,5544,2928],{"class":86},[33,5546,393],{"class":93},[33,5548,189],{"class":39},[33,5550,2935],{"class":50},[33,5552,2938],{"class":93},[33,5554,189],{"class":39},[33,5556,2943],{"class":43},[33,5558,2946],{"class":93},[33,5560,734],{"class":43},[33,5562,5564,5566,5568,5570],{"class":35,"line":5563},71,[33,5565,178],{"class":39},[33,5567,181],{"class":43},[33,5569,1232],{"class":86},[33,5571,734],{"class":43},[33,5573,5575],{"class":35,"line":5574},72,[33,5576,198],{"class":43},[33,5578,5580],{"class":35,"line":5579},73,[33,5581,58],{"emptyLinePlaceholder":57},[33,5583,5585,5587,5590],{"class":35,"line":5584},74,[33,5586,166],{"class":39},[33,5588,5589],{"class":93}," RequireAdmin",[33,5591,172],{"class":43},[33,5593,5595,5597,5599,5601,5603,5605,5607],{"class":35,"line":5594},75,[33,5596,247],{"class":39},[33,5598,644],{"class":43},[33,5600,1661],{"class":86},[33,5602,650],{"class":43},[33,5604,189],{"class":39},[33,5606,2855],{"class":93},[33,5608,258],{"class":43},[33,5610,5612,5614,5617,5619,5622,5624,5626,5628,5630,5632,5634,5636,5638],{"class":35,"line":5611},76,[33,5613,711],{"class":39},[33,5615,5616],{"class":43}," (user?.role ",[33,5618,2428],{"class":39},[33,5620,5621],{"class":50}," 'admin'",[33,5623,723],{"class":43},[33,5625,726],{"class":39},[33,5627,181],{"class":43},[33,5629,2928],{"class":86},[33,5631,393],{"class":93},[33,5633,189],{"class":39},[33,5635,3034],{"class":50},[33,5637,3037],{"class":93},[33,5639,734],{"class":43},[33,5641,5643,5645,5647,5649],{"class":35,"line":5642},77,[33,5644,178],{"class":39},[33,5646,181],{"class":43},[33,5648,1232],{"class":86},[33,5650,734],{"class":43},[33,5652,5654],{"class":35,"line":5653},78,[33,5655,198],{"class":43},[33,5657,5659],{"class":35,"line":5658},79,[33,5660,58],{"emptyLinePlaceholder":57},[33,5662,5664],{"class":35,"line":5663},80,[33,5665,5666],{"class":64},"\u002F\u002F --- Error Boundaries ---\n",[33,5668,5670,5672,5675],{"class":35,"line":5669},81,[33,5671,166],{"class":39},[33,5673,5674],{"class":93}," RootError",[33,5676,172],{"class":43},[33,5678,5680,5682,5684,5686,5688],{"class":35,"line":5679},82,[33,5681,247],{"class":39},[33,5683,2617],{"class":86},[33,5685,90],{"class":39},[33,5687,2622],{"class":93},[33,5689,258],{"class":43},[33,5691,5693,5695,5697,5699],{"class":35,"line":5692},83,[33,5694,711],{"class":39},[33,5696,1477],{"class":43},[33,5698,2637],{"class":93},[33,5700,2640],{"class":43},[33,5702,5704,5706,5708,5710,5712,5714,5716,5718,5721],{"class":35,"line":5703},84,[33,5705,1904],{"class":39},[33,5707,2652],{"class":43},[33,5709,717],{"class":39},[33,5711,1427],{"class":86},[33,5713,723],{"class":43},[33,5715,726],{"class":39},[33,5717,181],{"class":43},[33,5719,5720],{"class":86},"NotFoundPage",[33,5722,734],{"class":43},[33,5724,5726,5728,5730,5733,5736,5738,5741,5744,5746],{"class":35,"line":5725},85,[33,5727,2718],{"class":39},[33,5729,181],{"class":43},[33,5731,5732],{"class":86},"ServerErrorPage",[33,5734,5735],{"class":93}," status",[33,5737,189],{"class":39},[33,5739,5740],{"class":43},"{error.status} ",[33,5742,5743],{"class":93},"message",[33,5745,189],{"class":39},[33,5747,5748],{"class":43},"{error.statusText} \u002F>\n",[33,5750,5752],{"class":35,"line":5751},86,[33,5753,347],{"class":43},[33,5755,5757,5759,5761,5764,5766,5768],{"class":35,"line":5756},87,[33,5758,178],{"class":39},[33,5760,181],{"class":43},[33,5762,5763],{"class":86},"UnexpectedErrorPage",[33,5765,2617],{"class":93},[33,5767,189],{"class":39},[33,5769,5770],{"class":43},"{error} \u002F>\n",[33,5772,5774],{"class":35,"line":5773},88,[33,5775,198],{"class":43},[33,5777,5779],{"class":35,"line":5778},89,[33,5780,58],{"emptyLinePlaceholder":57},[33,5782,5784],{"class":35,"line":5783},90,[33,5785,5786],{"class":64},"\u002F\u002F --- Full Router Config ---\n",[33,5788,5790,5792,5794,5796,5798],{"class":35,"line":5789},91,[33,5791,83],{"class":39},[33,5793,87],{"class":86},[33,5795,90],{"class":39},[33,5797,94],{"class":93},[33,5799,97],{"class":43},[33,5801,5803],{"class":35,"line":5802},92,[33,5804,879],{"class":43},[33,5806,5808,5810,5812],{"class":35,"line":5807},93,[33,5809,884],{"class":43},[33,5811,106],{"class":50},[33,5813,889],{"class":43},[33,5815,5817,5819,5821],{"class":35,"line":5816},94,[33,5818,894],{"class":43},[33,5820,897],{"class":86},[33,5822,900],{"class":43},[33,5824,5826,5828,5830],{"class":35,"line":5825},95,[33,5827,905],{"class":43},[33,5829,908],{"class":86},[33,5831,900],{"class":43},[33,5833,5835],{"class":35,"line":5834},96,[33,5836,919],{"class":43},[33,5838,5840,5842,5844,5846,5848,5850,5852,5854,5856,5858,5860,5862,5864],{"class":35,"line":5839},97,[33,5841,924],{"class":43},[33,5843,333],{"class":86},[33,5845,109],{"class":43},[33,5847,3566],{"class":86},[33,5849,3569],{"class":93},[33,5851,189],{"class":39},[33,5853,3574],{"class":43},[33,5855,731],{"class":86},[33,5857,3580],{"class":43},[33,5859,112],{"class":86},[33,5861,1235],{"class":43},[33,5863,3566],{"class":86},[33,5865,5866],{"class":43},"> },\n",[33,5868,5870],{"class":35,"line":5869},98,[33,5871,969],{"class":43},[33,5873,5875,5877,5879],{"class":35,"line":5874},99,[33,5876,974],{"class":43},[33,5878,944],{"class":50},[33,5880,889],{"class":43},[33,5882,5884,5886,5888,5890,5892,5894,5896,5898,5901,5903,5905],{"class":35,"line":5883},100,[33,5885,984],{"class":43},[33,5887,3566],{"class":86},[33,5889,3569],{"class":93},[33,5891,189],{"class":39},[33,5893,3574],{"class":43},[33,5895,731],{"class":86},[33,5897,3580],{"class":43},[33,5899,5900],{"class":86},"ProductList",[33,5902,1235],{"class":43},[33,5904,3566],{"class":86},[33,5906,3589],{"class":43},[33,5908,5910],{"class":35,"line":5909},101,[33,5911,5912],{"class":43},"        loader: productsLoader,\n",[33,5914,5916],{"class":35,"line":5915},102,[33,5917,1054],{"class":43},[33,5919,5921],{"class":35,"line":5920},103,[33,5922,969],{"class":43},[33,5924,5926,5928,5930],{"class":35,"line":5925},104,[33,5927,974],{"class":43},[33,5929,958],{"class":50},[33,5931,889],{"class":43},[33,5933,5935,5937,5939,5941,5943,5945,5947,5949,5951,5953,5955],{"class":35,"line":5934},105,[33,5936,984],{"class":43},[33,5938,3566],{"class":86},[33,5940,3569],{"class":93},[33,5942,189],{"class":39},[33,5944,3574],{"class":43},[33,5946,731],{"class":86},[33,5948,3580],{"class":43},[33,5950,580],{"class":86},[33,5952,1235],{"class":43},[33,5954,3566],{"class":86},[33,5956,3589],{"class":43},[33,5958,5960],{"class":35,"line":5959},106,[33,5961,5962],{"class":43},"        loader: productDetailLoader,\n",[33,5964,5966],{"class":35,"line":5965},107,[33,5967,1054],{"class":43},[33,5969,5971],{"class":35,"line":5970},108,[33,5972,969],{"class":43},[33,5974,5976,5978,5981],{"class":35,"line":5975},109,[33,5977,974],{"class":43},[33,5979,5980],{"class":50},"'checkout'",[33,5982,889],{"class":43},[33,5984,5986,5988,5990,5992,5994,5996,5998,6000,6003,6005,6007],{"class":35,"line":5985},110,[33,5987,984],{"class":43},[33,5989,3566],{"class":86},[33,5991,3569],{"class":93},[33,5993,189],{"class":39},[33,5995,3574],{"class":43},[33,5997,731],{"class":86},[33,5999,3580],{"class":43},[33,6001,6002],{"class":86},"Checkout",[33,6004,1235],{"class":43},[33,6006,3566],{"class":86},[33,6008,3589],{"class":43},[33,6010,6012],{"class":35,"line":6011},111,[33,6013,6014],{"class":43},"        action: checkoutAction,\n",[33,6016,6018],{"class":35,"line":6017},112,[33,6019,1054],{"class":43},[33,6021,6023],{"class":35,"line":6022},113,[33,6024,1059],{"class":43},[33,6026,6028],{"class":35,"line":6027},114,[33,6029,1064],{"class":43},[33,6031,6033],{"class":35,"line":6032},115,[33,6034,879],{"class":43},[33,6036,6038,6040,6042],{"class":35,"line":6037},116,[33,6039,894],{"class":43},[33,6041,3080],{"class":86},[33,6043,900],{"class":43},[33,6045,6047],{"class":35,"line":6046},117,[33,6048,919],{"class":43},[33,6050,6052],{"class":35,"line":6051},118,[33,6053,969],{"class":43},[33,6055,6057,6059,6062],{"class":35,"line":6056},119,[33,6058,984],{"class":43},[33,6060,6061],{"class":86},"RequireAdmin",[33,6063,900],{"class":43},[33,6065,6067],{"class":35,"line":6066},120,[33,6068,998],{"class":43},[33,6070,6072],{"class":35,"line":6071},121,[33,6073,6074],{"class":43},"          {\n",[33,6076,6078,6081,6083],{"class":35,"line":6077},122,[33,6079,6080],{"class":43},"            path: ",[33,6082,3132],{"class":50},[33,6084,889],{"class":43},[33,6086,6088,6091,6094],{"class":35,"line":6087},123,[33,6089,6090],{"class":43},"            element: \u003C",[33,6092,6093],{"class":86},"AdminLayout",[33,6095,900],{"class":43},[33,6097,6099],{"class":35,"line":6098},124,[33,6100,6101],{"class":43},"            children: [\n",[33,6103,6105,6108,6110,6112,6114,6116,6118,6120,6122,6124,6127,6129,6131],{"class":35,"line":6104},125,[33,6106,6107],{"class":43},"              { index: ",[33,6109,333],{"class":86},[33,6111,109],{"class":43},[33,6113,3566],{"class":86},[33,6115,3569],{"class":93},[33,6117,189],{"class":39},[33,6119,3574],{"class":43},[33,6121,731],{"class":86},[33,6123,3580],{"class":43},[33,6125,6126],{"class":86},"AdminDashboard",[33,6128,1235],{"class":43},[33,6130,3566],{"class":86},[33,6132,5866],{"class":43},[33,6134,6136],{"class":35,"line":6135},126,[33,6137,6138],{"class":43},"            ],\n",[33,6140,6142],{"class":35,"line":6141},127,[33,6143,6144],{"class":43},"          },\n",[33,6146,6148],{"class":35,"line":6147},128,[33,6149,1049],{"class":43},[33,6151,6153],{"class":35,"line":6152},129,[33,6154,1054],{"class":43},[33,6156,6158],{"class":35,"line":6157},130,[33,6159,1059],{"class":43},[33,6161,6163],{"class":35,"line":6162},131,[33,6164,1064],{"class":43},[33,6166,6168,6170,6172,6174,6176],{"class":35,"line":6167},132,[33,6169,103],{"class":43},[33,6171,3176],{"class":50},[33,6173,109],{"class":43},[33,6175,3181],{"class":86},[33,6177,115],{"class":43},[33,6179,6181],{"class":35,"line":6180},133,[33,6182,155],{"class":43},[33,6184,6186],{"class":35,"line":6185},134,[33,6187,58],{"emptyLinePlaceholder":57},[33,6189,6191,6194,6197,6199,6201],{"class":35,"line":6190},135,[33,6192,6193],{"class":39},"export",[33,6195,6196],{"class":39}," default",[33,6198,270],{"class":39},[33,6200,169],{"class":93},[33,6202,172],{"class":43},[33,6204,6206,6208,6210,6212,6214,6216,6219,6222,6224,6226,6228],{"class":35,"line":6205},136,[33,6207,178],{"class":39},[33,6209,181],{"class":43},[33,6211,184],{"class":86},[33,6213,87],{"class":93},[33,6215,189],{"class":39},[33,6217,6218],{"class":43},"{router} ",[33,6220,6221],{"class":93},"fallbackElement",[33,6223,189],{"class":39},[33,6225,3574],{"class":43},[33,6227,731],{"class":86},[33,6229,6230],{"class":43}," \u002F>} \u002F>\n",[33,6232,6234],{"class":35,"line":6233},137,[33,6235,198],{"class":43},[14,6237,6239],{"id":6238},"anti-pattern-common-routing-mistakes","Anti-Pattern — Common Routing Mistakes",[19,6241,6243],{"filename":6242,"language":22},"anti_patterns.js",[24,6244,6246],{"className":26,"code":6245,"language":22,"meta":28,"style":28},"\u002F\u002F ANTI-PATTERN: BrowserRouter with deeply nested \u003CRoutes> everywhere\n\u002F\u002F instead of a single config. Scattered route definitions, no loaders,\n\u002F\u002F no error boundaries, no data revalidation.\nfunction App() {\n  return (\n    \u003CBrowserRouter>\n      \u003CRoutes>\n        \u003CRoute path=\"\u002F\" element={\u003CHome \u002F>} \u002F>\n        \u003CRoute path=\"\u002Fproducts\" element={\u003CProducts \u002F>}>\n          \u003CRoute path=\":id\" element={\u003CDetail \u002F>} \u002F>\n        \u003C\u002FRoute>\n      \u003C\u002FRoutes>\n    \u003C\u002FBrowserRouter>\n  )\n}\n\u002F\u002F This works but loses: loaders, actions, errorElement, useLoaderData,\n\u002F\u002F automatic revalidation, typed routes, and the developer experience\n\u002F\u002F of a single route config tree. Use createBrowserRouter.\n\n\u002F\u002F ANTI-PATTERN: Comparing useParams to a number\nif (productId === 42) { \u002F* never true — productId is \"42\" *\u002F }\n\u002F\u002F Fix: if (Number(productId) === 42) { \u002F* works *\u002F }\n\n\u002F\u002F ANTI-PATTERN: Fetching inside useEffect when a loader exists\nfunction ProductDetail() {\n  const { productId } = useParams()\n  const [product, setProduct] = useState(null)\n  useEffect(() => {\n    fetch(`\u002Fapi\u002Fproducts\u002F${productId}`).then(r => r.json()).then(setProduct)\n  }, [productId])  \u002F\u002F loading flash on every navigation, no abort, race conditions\n  if (!product) return \u003CSpinner \u002F>\n  return \u003Ch1>{product.name}\u003C\u002Fh1>\n}\n\u002F\u002F Fix: use a loader — data available before render, no flash, built-in abort.\n\n\u002F\u002F ANTI-PATTERN: \u003CNavigate> without replace in auth guards\nif (!user) return \u003CNavigate to=\"\u002Flogin\" \u002F>\n\u002F\u002F Back button -> protected page -> redirect -> \u002Flogin -> loop. Always use replace.\n",[30,6247,6248,6253,6258,6263,6271,6277,6286,6295,6320,6343,6367,6375,6383,6391,6395,6399,6404,6409,6414,6418,6423,6444,6449,6453,6458,6466,6482,6508,6518,6553,6561,6580,6594,6598,6603,6607,6612,6636],{"__ignoreMap":28},[33,6249,6250],{"class":35,"line":36},[33,6251,6252],{"class":64},"\u002F\u002F ANTI-PATTERN: BrowserRouter with deeply nested \u003CRoutes> everywhere\n",[33,6254,6255],{"class":35,"line":54},[33,6256,6257],{"class":64},"\u002F\u002F instead of a single config. Scattered route definitions, no loaders,\n",[33,6259,6260],{"class":35,"line":61},[33,6261,6262],{"class":64},"\u002F\u002F no error boundaries, no data revalidation.\n",[33,6264,6265,6267,6269],{"class":35,"line":68},[33,6266,166],{"class":39},[33,6268,169],{"class":93},[33,6270,172],{"class":43},[33,6272,6273,6275],{"class":35,"line":74},[33,6274,178],{"class":39},[33,6276,358],{"class":43},[33,6278,6279,6281,6284],{"class":35,"line":80},[33,6280,363],{"class":43},[33,6282,6283],{"class":86},"BrowserRouter",[33,6285,370],{"class":43},[33,6287,6288,6290,6293],{"class":35,"line":100},[33,6289,387],{"class":43},[33,6291,6292],{"class":86},"Routes",[33,6294,370],{"class":43},[33,6296,6297,6299,6302,6304,6306,6308,6311,6313,6315,6318],{"class":35,"line":118},[33,6298,1139],{"class":43},[33,6300,6301],{"class":86},"Route",[33,6303,841],{"class":93},[33,6305,189],{"class":39},[33,6307,490],{"class":50},[33,6309,6310],{"class":93}," element",[33,6312,189],{"class":39},[33,6314,3574],{"class":43},[33,6316,6317],{"class":86},"Home",[33,6319,6230],{"class":43},[33,6321,6322,6324,6326,6328,6330,6332,6334,6336,6338,6341],{"class":35,"line":133},[33,6323,1139],{"class":43},[33,6325,6301],{"class":86},[33,6327,841],{"class":93},[33,6329,189],{"class":39},[33,6331,1170],{"class":50},[33,6333,6310],{"class":93},[33,6335,189],{"class":39},[33,6337,3574],{"class":43},[33,6339,6340],{"class":86},"Products",[33,6342,3887],{"class":43},[33,6344,6345,6347,6349,6351,6353,6356,6358,6360,6362,6365],{"class":35,"line":152},[33,6346,3892],{"class":43},[33,6348,6301],{"class":86},[33,6350,841],{"class":93},[33,6352,189],{"class":39},[33,6354,6355],{"class":50},"\":id\"",[33,6357,6310],{"class":93},[33,6359,189],{"class":39},[33,6361,3574],{"class":43},[33,6363,6364],{"class":86},"Detail",[33,6366,6230],{"class":43},[33,6368,6369,6371,6373],{"class":35,"line":158},[33,6370,3901],{"class":43},[33,6372,6301],{"class":86},[33,6374,370],{"class":43},[33,6376,6377,6379,6381],{"class":35,"line":163},[33,6378,1204],{"class":43},[33,6380,6292],{"class":86},[33,6382,370],{"class":43},[33,6384,6385,6387,6389],{"class":35,"line":175},[33,6386,442],{"class":43},[33,6388,6283],{"class":86},[33,6390,370],{"class":43},[33,6392,6393],{"class":35,"line":195},[33,6394,452],{"class":43},[33,6396,6397],{"class":35,"line":373},[33,6398,198],{"class":43},[33,6400,6401],{"class":35,"line":384},[33,6402,6403],{"class":64},"\u002F\u002F This works but loses: loaders, actions, errorElement, useLoaderData,\n",[33,6405,6406],{"class":35,"line":419},[33,6407,6408],{"class":64},"\u002F\u002F automatic revalidation, typed routes, and the developer experience\n",[33,6410,6411],{"class":35,"line":439},[33,6412,6413],{"class":64},"\u002F\u002F of a single route config tree. Use createBrowserRouter.\n",[33,6415,6416],{"class":35,"line":449},[33,6417,58],{"emptyLinePlaceholder":57},[33,6419,6420],{"class":35,"line":455},[33,6421,6422],{"class":64},"\u002F\u002F ANTI-PATTERN: Comparing useParams to a number\n",[33,6424,6425,6428,6431,6433,6436,6439,6442],{"class":35,"line":460},[33,6426,6427],{"class":39},"if",[33,6429,6430],{"class":43}," (productId ",[33,6432,717],{"class":39},[33,6434,6435],{"class":86}," 42",[33,6437,6438],{"class":43},") { ",[33,6440,6441],{"class":64},"\u002F* never true — productId is \"42\" *\u002F",[33,6443,5222],{"class":43},[33,6445,6446],{"class":35,"line":465},[33,6447,6448],{"class":64},"\u002F\u002F Fix: if (Number(productId) === 42) { \u002F* works *\u002F }\n",[33,6450,6451],{"class":35,"line":471},[33,6452,58],{"emptyLinePlaceholder":57},[33,6454,6455],{"class":35,"line":477},[33,6456,6457],{"class":64},"\u002F\u002F ANTI-PATTERN: Fetching inside useEffect when a loader exists\n",[33,6459,6460,6462,6464],{"class":35,"line":531},[33,6461,166],{"class":39},[33,6463,635],{"class":93},[33,6465,172],{"class":43},[33,6467,6468,6470,6472,6474,6476,6478,6480],{"class":35,"line":537},[33,6469,247],{"class":39},[33,6471,644],{"class":43},[33,6473,647],{"class":86},[33,6475,650],{"class":43},[33,6477,189],{"class":39},[33,6479,655],{"class":93},[33,6481,258],{"class":43},[33,6483,6484,6486,6488,6490,6492,6495,6497,6499,6501,6503,6506],{"class":35,"line":1100},[33,6485,247],{"class":39},[33,6487,1658],{"class":43},[33,6489,239],{"class":86},[33,6491,684],{"class":43},[33,6493,6494],{"class":86},"setProduct",[33,6496,823],{"class":43},[33,6498,189],{"class":39},[33,6500,4542],{"class":93},[33,6502,312],{"class":43},[33,6504,6505],{"class":86},"null",[33,6507,706],{"class":43},[33,6509,6510,6512,6514,6516],{"class":35,"line":1110},[33,6511,4588],{"class":93},[33,6513,3387],{"class":43},[33,6515,510],{"class":39},[33,6517,1364],{"class":43},[33,6519,6520,6522,6524,6526,6528,6530,6533,6535,6537,6539,6541,6543,6545,6548,6550],{"class":35,"line":1117},[33,6521,1692],{"class":93},[33,6523,312],{"class":43},[33,6525,699],{"class":50},[33,6527,647],{"class":43},[33,6529,327],{"class":50},[33,6531,6532],{"class":43},").",[33,6534,1703],{"class":93},[33,6536,312],{"class":43},[33,6538,1708],{"class":238},[33,6540,1711],{"class":39},[33,6542,1714],{"class":43},[33,6544,1513],{"class":93},[33,6546,6547],{"class":43},"()).",[33,6549,1703],{"class":93},[33,6551,6552],{"class":43},"(setProduct)\n",[33,6554,6555,6558],{"class":35,"line":1126},[33,6556,6557],{"class":43},"  }, [productId])  ",[33,6559,6560],{"class":64},"\u002F\u002F loading flash on every navigation, no abort, race conditions\n",[33,6562,6563,6565,6567,6569,6572,6574,6576,6578],{"class":35,"line":1136},[33,6564,711],{"class":39},[33,6566,1477],{"class":43},[33,6568,1480],{"class":39},[33,6570,6571],{"class":43},"product) ",[33,6573,726],{"class":39},[33,6575,181],{"class":43},[33,6577,731],{"class":86},[33,6579,734],{"class":43},[33,6581,6582,6584,6586,6588,6590,6592],{"class":35,"line":1159},[33,6583,178],{"class":39},[33,6585,181],{"class":43},[33,6587,10],{"class":366},[33,6589,767],{"class":43},[33,6591,10],{"class":366},[33,6593,370],{"class":43},[33,6595,6596],{"class":35,"line":1180},[33,6597,198],{"class":43},[33,6599,6600],{"class":35,"line":1201},[33,6601,6602],{"class":64},"\u002F\u002F Fix: use a loader — data available before render, no flash, built-in abort.\n",[33,6604,6605],{"class":35,"line":1211},[33,6606,58],{"emptyLinePlaceholder":57},[33,6608,6609],{"class":35,"line":1221},[33,6610,6611],{"class":64},"\u002F\u002F ANTI-PATTERN: \u003CNavigate> without replace in auth guards\n",[33,6613,6614,6616,6618,6620,6622,6624,6626,6628,6630,6632,6634],{"class":35,"line":1242},[33,6615,6427],{"class":39},[33,6617,1477],{"class":43},[33,6619,1480],{"class":39},[33,6621,5538],{"class":43},[33,6623,726],{"class":39},[33,6625,181],{"class":43},[33,6627,2928],{"class":86},[33,6629,393],{"class":93},[33,6631,189],{"class":39},[33,6633,2935],{"class":50},[33,6635,734],{"class":43},[33,6637,6638],{"class":35,"line":1252},[33,6639,6640],{"class":64},"\u002F\u002F Back button -> protected page -> redirect -> \u002Flogin -> loop. Always use replace.\n",[14,6642,6644],{"id":6643},"tips-tricks","💡 Tips & Tricks",[19,6646,6648],{"filename":6647,"language":22},"tips.js",[24,6649,6651],{"className":26,"code":6650,"language":22,"meta":28,"style":28},"\u002F\u002F [Idiom] Use \u003CLink>\u002F\u003CNavLink> for direct-click navigation; useNavigate for\n\u002F\u002F navigation as a side effect (after form submit, async completion). Mixing\n\u002F\u002F them inconsistently breaks middle-click\u002Fright-click browser affordances.\n\n\u002F\u002F [Idiom] Prefer loaders over useEffect-fetch for data a page MUST have to\n\u002F\u002F render. Removes the loading-flash class of UX issues entirely and sidesteps\n\u002F\u002F fetch-on-mount race conditions (Ch 17). The loader's request.signal handles\n\u002F\u002F abort automatically when the user navigates away mid-load.\n\n\u002F\u002F [Idiom] Store filter\u002Fsort\u002Fpagination state in the URL via useSearchParams —\n\u002F\u002F shareable, bookmarkable, refresh-surviving. Local useState for the same\n\u002F\u002F state is lost on refresh and can't be shared via a URL.\n\n\u002F\u002F [Debug] useNavigate(-1) \u002F useNavigate(1) navigate browser history — but\n\u002F\u002F only works if there IS history to go back to. On a direct entry (user\n\u002F\u002F typed the URL or opened a link), there's no previous entry. Use\n\u002F\u002F navigate(location.state?.from || '\u002Fdefault') as a fallback.\n\n\u002F\u002F [Idiom] Use { replace: true } on setSearchParams for transient state\n\u002F\u002F (search-as-you-type) to avoid flooding browser history with one entry\n\u002F\u002F per keystroke. Use default (push) for discrete state changes (page\n\u002F\u002F navigation, filter application).\n\n\u002F\u002F [Performance] lazy() + Suspense at route boundaries is the highest-leverage\n\u002F\u002F code split — users only download code for routes they visit. The loader\n\u002F\u002F runs in parallel with the chunk download, so data + code arrive together.\n",[30,6652,6653,6658,6663,6668,6672,6677,6682,6687,6692,6696,6701,6706,6711,6715,6720,6725,6730,6735,6739,6744,6749,6754,6759,6763,6768,6773],{"__ignoreMap":28},[33,6654,6655],{"class":35,"line":36},[33,6656,6657],{"class":64},"\u002F\u002F [Idiom] Use \u003CLink>\u002F\u003CNavLink> for direct-click navigation; useNavigate for\n",[33,6659,6660],{"class":35,"line":54},[33,6661,6662],{"class":64},"\u002F\u002F navigation as a side effect (after form submit, async completion). Mixing\n",[33,6664,6665],{"class":35,"line":61},[33,6666,6667],{"class":64},"\u002F\u002F them inconsistently breaks middle-click\u002Fright-click browser affordances.\n",[33,6669,6670],{"class":35,"line":68},[33,6671,58],{"emptyLinePlaceholder":57},[33,6673,6674],{"class":35,"line":74},[33,6675,6676],{"class":64},"\u002F\u002F [Idiom] Prefer loaders over useEffect-fetch for data a page MUST have to\n",[33,6678,6679],{"class":35,"line":80},[33,6680,6681],{"class":64},"\u002F\u002F render. Removes the loading-flash class of UX issues entirely and sidesteps\n",[33,6683,6684],{"class":35,"line":100},[33,6685,6686],{"class":64},"\u002F\u002F fetch-on-mount race conditions (Ch 17). The loader's request.signal handles\n",[33,6688,6689],{"class":35,"line":118},[33,6690,6691],{"class":64},"\u002F\u002F abort automatically when the user navigates away mid-load.\n",[33,6693,6694],{"class":35,"line":133},[33,6695,58],{"emptyLinePlaceholder":57},[33,6697,6698],{"class":35,"line":152},[33,6699,6700],{"class":64},"\u002F\u002F [Idiom] Store filter\u002Fsort\u002Fpagination state in the URL via useSearchParams —\n",[33,6702,6703],{"class":35,"line":158},[33,6704,6705],{"class":64},"\u002F\u002F shareable, bookmarkable, refresh-surviving. Local useState for the same\n",[33,6707,6708],{"class":35,"line":163},[33,6709,6710],{"class":64},"\u002F\u002F state is lost on refresh and can't be shared via a URL.\n",[33,6712,6713],{"class":35,"line":175},[33,6714,58],{"emptyLinePlaceholder":57},[33,6716,6717],{"class":35,"line":195},[33,6718,6719],{"class":64},"\u002F\u002F [Debug] useNavigate(-1) \u002F useNavigate(1) navigate browser history — but\n",[33,6721,6722],{"class":35,"line":373},[33,6723,6724],{"class":64},"\u002F\u002F only works if there IS history to go back to. On a direct entry (user\n",[33,6726,6727],{"class":35,"line":384},[33,6728,6729],{"class":64},"\u002F\u002F typed the URL or opened a link), there's no previous entry. Use\n",[33,6731,6732],{"class":35,"line":419},[33,6733,6734],{"class":64},"\u002F\u002F navigate(location.state?.from || '\u002Fdefault') as a fallback.\n",[33,6736,6737],{"class":35,"line":439},[33,6738,58],{"emptyLinePlaceholder":57},[33,6740,6741],{"class":35,"line":449},[33,6742,6743],{"class":64},"\u002F\u002F [Idiom] Use { replace: true } on setSearchParams for transient state\n",[33,6745,6746],{"class":35,"line":455},[33,6747,6748],{"class":64},"\u002F\u002F (search-as-you-type) to avoid flooding browser history with one entry\n",[33,6750,6751],{"class":35,"line":460},[33,6752,6753],{"class":64},"\u002F\u002F per keystroke. Use default (push) for discrete state changes (page\n",[33,6755,6756],{"class":35,"line":465},[33,6757,6758],{"class":64},"\u002F\u002F navigation, filter application).\n",[33,6760,6761],{"class":35,"line":471},[33,6762,58],{"emptyLinePlaceholder":57},[33,6764,6765],{"class":35,"line":477},[33,6766,6767],{"class":64},"\u002F\u002F [Performance] lazy() + Suspense at route boundaries is the highest-leverage\n",[33,6769,6770],{"class":35,"line":531},[33,6771,6772],{"class":64},"\u002F\u002F code split — users only download code for routes they visit. The loader\n",[33,6774,6775],{"class":35,"line":537},[33,6776,6777],{"class":64},"\u002F\u002F runs in parallel with the chunk download, so data + code arrive together.\n",[14,6779,6781],{"id":6780},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[19,6783,6785],{"filename":6784,"language":22},"edge_cases.js",[24,6786,6788],{"className":26,"code":6787,"language":22,"meta":28,"style":28},"\u002F\u002F [Gotcha] useParams values are ALWAYS strings. \u002Fproducts\u002F42 -> \"42\".\n\u002F\u002F Comparing productId === 42 (number) is never true. Use Number(productId).\n\n\u002F\u002F [Gotcha] A nested layout's parent element does NOT remount when navigating\n\u002F\u002F between children — state, scroll position, and open UI persist by design.\n\u002F\u002F If you need a \"clean slate\" on each child, move that state into the child.\n\n\u002F\u002F [Gotcha] \u003CNavLink to=\"\u002F\"> without `end` matches EVERY route (every path\n\u002F\u002F starts with \u002F). Always add `end` to root or exact-match NavLinks.\n\n\u002F\u002F [Gotcha] Loaders re-run on back\u002Fforward browser navigation, not just\n\u002F\u002F forward navigation. A loader with side effects (analytics, logging) fires\n\u002F\u002F far more often than a useEffect-based approach would.\n\n\u002F\u002F [Gotcha] isRouteErrorResponse() returns true ONLY for thrown Response\n\u002F\u002F objects. A thrown plain Error has no .status — handle it separately\n\u002F\u002F in the same errorElement.\n\n\u002F\u002F [Gotcha] setSearchParams on every keystroke floods browser history.\n\u002F\u002F Use local state + debounce + setSearchParams({ replace: true }) for\n\u002F\u002F search-as-you-type patterns.\n\n\u002F\u002F [Gotcha] useSearchParams().get('key') returns null if the param is\n\u002F\u002F absent, NOT an empty string. Falsy checks like `if (searchParams.get('q'))`\n\u002F\u002F work, but `searchParams.get('q').length` throws on null — guard it.\n\n\u002F\u002F [Gotcha] Lazy-loaded routes without a Suspense ancestor throw at runtime,\n\u002F\u002F not merely warn. Every lazy() component needs a \u003CSuspense> somewhere\n\u002F\u002F above it in the tree, or the app crashes on first load of that route.\n\n\u002F\u002F [Gotcha] The `request.signal` in a loader is aborted when the user\n\u002F\u002F navigates away. If you ignore it (don't pass it to fetch), the fetch\n\u002F\u002F completes in the background and you may get an unhandled rejection\n\u002F\u002F if the response handling code runs after the route unmounted.\n",[30,6789,6790,6795,6800,6804,6809,6814,6819,6823,6828,6833,6837,6842,6847,6852,6856,6861,6866,6871,6875,6880,6885,6890,6894,6899,6904,6909,6913,6918,6923,6928,6932,6937,6942,6947],{"__ignoreMap":28},[33,6791,6792],{"class":35,"line":36},[33,6793,6794],{"class":64},"\u002F\u002F [Gotcha] useParams values are ALWAYS strings. \u002Fproducts\u002F42 -> \"42\".\n",[33,6796,6797],{"class":35,"line":54},[33,6798,6799],{"class":64},"\u002F\u002F Comparing productId === 42 (number) is never true. Use Number(productId).\n",[33,6801,6802],{"class":35,"line":61},[33,6803,58],{"emptyLinePlaceholder":57},[33,6805,6806],{"class":35,"line":68},[33,6807,6808],{"class":64},"\u002F\u002F [Gotcha] A nested layout's parent element does NOT remount when navigating\n",[33,6810,6811],{"class":35,"line":74},[33,6812,6813],{"class":64},"\u002F\u002F between children — state, scroll position, and open UI persist by design.\n",[33,6815,6816],{"class":35,"line":80},[33,6817,6818],{"class":64},"\u002F\u002F If you need a \"clean slate\" on each child, move that state into the child.\n",[33,6820,6821],{"class":35,"line":100},[33,6822,58],{"emptyLinePlaceholder":57},[33,6824,6825],{"class":35,"line":118},[33,6826,6827],{"class":64},"\u002F\u002F [Gotcha] \u003CNavLink to=\"\u002F\"> without `end` matches EVERY route (every path\n",[33,6829,6830],{"class":35,"line":133},[33,6831,6832],{"class":64},"\u002F\u002F starts with \u002F). Always add `end` to root or exact-match NavLinks.\n",[33,6834,6835],{"class":35,"line":152},[33,6836,58],{"emptyLinePlaceholder":57},[33,6838,6839],{"class":35,"line":158},[33,6840,6841],{"class":64},"\u002F\u002F [Gotcha] Loaders re-run on back\u002Fforward browser navigation, not just\n",[33,6843,6844],{"class":35,"line":163},[33,6845,6846],{"class":64},"\u002F\u002F forward navigation. A loader with side effects (analytics, logging) fires\n",[33,6848,6849],{"class":35,"line":175},[33,6850,6851],{"class":64},"\u002F\u002F far more often than a useEffect-based approach would.\n",[33,6853,6854],{"class":35,"line":195},[33,6855,58],{"emptyLinePlaceholder":57},[33,6857,6858],{"class":35,"line":373},[33,6859,6860],{"class":64},"\u002F\u002F [Gotcha] isRouteErrorResponse() returns true ONLY for thrown Response\n",[33,6862,6863],{"class":35,"line":384},[33,6864,6865],{"class":64},"\u002F\u002F objects. A thrown plain Error has no .status — handle it separately\n",[33,6867,6868],{"class":35,"line":419},[33,6869,6870],{"class":64},"\u002F\u002F in the same errorElement.\n",[33,6872,6873],{"class":35,"line":439},[33,6874,58],{"emptyLinePlaceholder":57},[33,6876,6877],{"class":35,"line":449},[33,6878,6879],{"class":64},"\u002F\u002F [Gotcha] setSearchParams on every keystroke floods browser history.\n",[33,6881,6882],{"class":35,"line":455},[33,6883,6884],{"class":64},"\u002F\u002F Use local state + debounce + setSearchParams({ replace: true }) for\n",[33,6886,6887],{"class":35,"line":460},[33,6888,6889],{"class":64},"\u002F\u002F search-as-you-type patterns.\n",[33,6891,6892],{"class":35,"line":465},[33,6893,58],{"emptyLinePlaceholder":57},[33,6895,6896],{"class":35,"line":471},[33,6897,6898],{"class":64},"\u002F\u002F [Gotcha] useSearchParams().get('key') returns null if the param is\n",[33,6900,6901],{"class":35,"line":477},[33,6902,6903],{"class":64},"\u002F\u002F absent, NOT an empty string. Falsy checks like `if (searchParams.get('q'))`\n",[33,6905,6906],{"class":35,"line":531},[33,6907,6908],{"class":64},"\u002F\u002F work, but `searchParams.get('q').length` throws on null — guard it.\n",[33,6910,6911],{"class":35,"line":537},[33,6912,58],{"emptyLinePlaceholder":57},[33,6914,6915],{"class":35,"line":1100},[33,6916,6917],{"class":64},"\u002F\u002F [Gotcha] Lazy-loaded routes without a Suspense ancestor throw at runtime,\n",[33,6919,6920],{"class":35,"line":1110},[33,6921,6922],{"class":64},"\u002F\u002F not merely warn. Every lazy() component needs a \u003CSuspense> somewhere\n",[33,6924,6925],{"class":35,"line":1117},[33,6926,6927],{"class":64},"\u002F\u002F above it in the tree, or the app crashes on first load of that route.\n",[33,6929,6930],{"class":35,"line":1126},[33,6931,58],{"emptyLinePlaceholder":57},[33,6933,6934],{"class":35,"line":1136},[33,6935,6936],{"class":64},"\u002F\u002F [Gotcha] The `request.signal` in a loader is aborted when the user\n",[33,6938,6939],{"class":35,"line":1159},[33,6940,6941],{"class":64},"\u002F\u002F navigates away. If you ignore it (don't pass it to fetch), the fetch\n",[33,6943,6944],{"class":35,"line":1180},[33,6945,6946],{"class":64},"\u002F\u002F completes in the background and you may get an unhandled rejection\n",[33,6948,6949],{"class":35,"line":1201},[33,6950,6951],{"class":64},"\u002F\u002F if the response handling code runs after the route unmounted.\n",[14,6953,6955],{"id":6954},"spot-the-bug","🧠 Spot the Bug",[1591,6957,6958,6959,6962,6963,6966],{},"A product detail page uses a loader to fetch data, but navigating between products (",[30,6960,6961],{},"\u002Fproducts\u002F1"," → ",[30,6964,6965],{},"\u002Fproducts\u002F2",") shows the OLD product's data briefly before updating.",[19,6968,6970],{"filename":6969,"language":22},"spot_the_bug.js",[24,6971,6973],{"className":26,"code":6972,"language":22,"meta":28,"style":28},"function ProductDetail() {\n  const product = useLoaderData()\n  return (\n    \u003Cdiv>\n      \u003Ch1>{product.name}\u003C\u002Fh1>\n      \u003Cp>{product.description}\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  )\n}\n\n\u002F\u002F The loader:\nasync function productLoader({ params }) {\n  const res = await fetch(`\u002Fapi\u002Fproducts\u002F${params.productId}`)\n  return res.json()\n}\n",[30,6974,6975,6983,6995,7001,7009,7021,7034,7042,7046,7050,7054,7059,7074,7100,7110],{"__ignoreMap":28},[33,6976,6977,6979,6981],{"class":35,"line":36},[33,6978,166],{"class":39},[33,6980,635],{"class":93},[33,6982,172],{"class":43},[33,6984,6985,6987,6989,6991,6993],{"class":35,"line":54},[33,6986,247],{"class":39},[33,6988,1547],{"class":86},[33,6990,90],{"class":39},[33,6992,1552],{"class":93},[33,6994,258],{"class":43},[33,6996,6997,6999],{"class":35,"line":61},[33,6998,178],{"class":39},[33,7000,358],{"class":43},[33,7002,7003,7005,7007],{"class":35,"line":68},[33,7004,363],{"class":43},[33,7006,367],{"class":366},[33,7008,370],{"class":43},[33,7010,7011,7013,7015,7017,7019],{"class":35,"line":74},[33,7012,387],{"class":43},[33,7014,10],{"class":366},[33,7016,767],{"class":43},[33,7018,10],{"class":366},[33,7020,370],{"class":43},[33,7022,7023,7025,7027,7030,7032],{"class":35,"line":80},[33,7024,387],{"class":43},[33,7026,1591],{"class":366},[33,7028,7029],{"class":43},">{product.description}\u003C\u002F",[33,7031,1591],{"class":366},[33,7033,370],{"class":43},[33,7035,7036,7038,7040],{"class":35,"line":100},[33,7037,442],{"class":43},[33,7039,367],{"class":366},[33,7041,370],{"class":43},[33,7043,7044],{"class":35,"line":118},[33,7045,452],{"class":43},[33,7047,7048],{"class":35,"line":133},[33,7049,198],{"class":43},[33,7051,7052],{"class":35,"line":152},[33,7053,58],{"emptyLinePlaceholder":57},[33,7055,7056],{"class":35,"line":158},[33,7057,7058],{"class":64},"\u002F\u002F The loader:\n",[33,7060,7061,7063,7065,7068,7070,7072],{"class":35,"line":163},[33,7062,1346],{"class":39},[33,7064,270],{"class":39},[33,7066,7067],{"class":93}," productLoader",[33,7069,235],{"class":43},[33,7071,1352],{"class":238},[33,7073,242],{"class":43},[33,7075,7076,7078,7080,7082,7084,7086,7088,7090,7092,7094,7096,7098],{"class":35,"line":175},[33,7077,247],{"class":39},[33,7079,1382],{"class":86},[33,7081,90],{"class":39},[33,7083,288],{"class":39},[33,7085,1389],{"class":93},[33,7087,312],{"class":43},[33,7089,699],{"class":50},[33,7091,1352],{"class":43},[33,7093,321],{"class":50},[33,7095,647],{"class":43},[33,7097,327],{"class":50},[33,7099,706],{"class":43},[33,7101,7102,7104,7106,7108],{"class":35,"line":195},[33,7103,178],{"class":39},[33,7105,1510],{"class":43},[33,7107,1513],{"class":93},[33,7109,258],{"class":43},[33,7111,7112],{"class":35,"line":373},[33,7113,198],{"class":43},[7115,7116,7117,7121,7148,7158,7239],"details",{},[7118,7119,7120],"summary",{},"Answer",[1591,7122,7123,7126,7127,7131,7132,7134,7135,7137,7138,7140,7141,7144,7145,7147],{},[30,7124,7125],{},"useLoaderData"," returns the data from the ",[7128,7129,7130],"strong",{},"most recent completed navigation's"," loader. When navigating from ",[30,7133,6961],{}," to ",[30,7136,6965],{},", React Router starts the new loader, but the ",[30,7139,580],{}," component is still mounted with the ",[7128,7142,7143],{},"old"," data until the new loader resolves. During that window, the component renders with ",[30,7146,239],{}," still pointing to product 1's data — a stale flash.",[1591,7149,7150,7153,7154,7157],{},[7128,7151,7152],{},"Fix",": Use ",[30,7155,7156],{},"useNavigation()"," to detect when a loader is in-flight and show a loading indicator or disable the content:",[24,7159,7161],{"className":26,"code":7160,"language":22,"meta":28,"style":28},"function ProductDetail() {\n  const product = useLoaderData()\n  const navigation = useNavigation()\n  \u002F\u002F navigation.state is 'loading' while the new loader runs\n  if (navigation.state === 'loading') return \u003CSpinner \u002F>\n  return \u003Ch1>{product.name}\u003C\u002Fh1>\n}\n",[30,7162,7163,7171,7183,7195,7200,7221,7235],{"__ignoreMap":28},[33,7164,7165,7167,7169],{"class":35,"line":36},[33,7166,166],{"class":39},[33,7168,635],{"class":93},[33,7170,172],{"class":43},[33,7172,7173,7175,7177,7179,7181],{"class":35,"line":54},[33,7174,247],{"class":39},[33,7176,1547],{"class":86},[33,7178,90],{"class":39},[33,7180,1552],{"class":93},[33,7182,258],{"class":43},[33,7184,7185,7187,7189,7191,7193],{"class":35,"line":61},[33,7186,247],{"class":39},[33,7188,2324],{"class":86},[33,7190,90],{"class":39},[33,7192,2329],{"class":93},[33,7194,258],{"class":43},[33,7196,7197],{"class":35,"line":68},[33,7198,7199],{"class":64},"  \u002F\u002F navigation.state is 'loading' while the new loader runs\n",[33,7201,7202,7204,7207,7209,7211,7213,7215,7217,7219],{"class":35,"line":74},[33,7203,711],{"class":39},[33,7205,7206],{"class":43}," (navigation.state ",[33,7208,717],{"class":39},[33,7210,720],{"class":50},[33,7212,723],{"class":43},[33,7214,726],{"class":39},[33,7216,181],{"class":43},[33,7218,731],{"class":86},[33,7220,734],{"class":43},[33,7222,7223,7225,7227,7229,7231,7233],{"class":35,"line":80},[33,7224,178],{"class":39},[33,7226,181],{"class":43},[33,7228,10],{"class":366},[33,7230,767],{"class":43},[33,7232,10],{"class":366},[33,7234,370],{"class":43},[33,7236,7237],{"class":35,"line":100},[33,7238,198],{"class":43},[1591,7240,7241,7242,7245],{},"Alternatively, wrap the route in ",[30,7243,7244],{},"\u003CSuspense>"," so React Router shows the fallback during the loader fetch instead of stale content.",[14,7247,7249],{"id":7248},"key-takeaways","Key Takeaways",[19,7251,7253],{"filename":7252,"language":22},"key_takeaways.js",[24,7254,7256],{"className":26,"code":7255,"language":22,"meta":28,"style":28},"\u002F\u002F 1. createBrowserRouter + \u003CRouterProvider> is the recommended v6.4+ API —\n\u002F\u002F    supports loaders, actions, errorElement, and automatic revalidation.\n\u002F\u002F    The older \u003CBrowserRouter>\u002F\u003CRoutes> JSX API lacks these features.\n\n\u002F\u002F 2. \u003CLink>\u002F\u003CNavLink> for direct-click navigation (preserves \u003Ca> affordances);\n\u002F\u002F    useNavigate for imperative navigation after an action completes.\n\u002F\u002F    Always use { replace: true } on auth-redirect \u003CNavigate> to avoid\n\u002F\u002F    back-button redirect loops.\n\n\u002F\u002F 3. Nested routes + \u003COutlet> = persistent layouts that don't remount\n\u002F\u002F    between child navigations. State, scroll position, and UI persist.\n\n\u002F\u002F 4. Loaders fetch data BEFORE the route renders — no loading flash,\n\u002F\u002F    automatic abort via request.signal, and data ready via useLoaderData.\n\u002F\u002F    Actions handle mutations and automatically revalidate active loaders.\n\n\u002F\u002F 5. Protected routes need THREE auth states: loading, authenticated,\n\u002F\u002F    unauthenticated. Two-state (user | null) flashes a redirect for\n\u002F\u002F    every logged-in user during the initial async check.\n\n\u002F\u002F 6. lazy() + Suspense at route boundaries = highest-leverage code split.\n\u002F\u002F    The chunk download runs in parallel with the loader — data + code\n\u002F\u002F    arrive together. Every lazy route needs a Suspense ancestor.\n\n\u002F\u002F 7. useSearchParams for shareable\u002Frefresh-surviving view state (filters,\n\u002F\u002F    sort, pagination). Debounce + { replace: true } for search-as-you-type\n\u002F\u002F    to avoid flooding browser history with per-keystroke entries.\n",[30,7257,7258,7263,7268,7273,7277,7282,7287,7292,7297,7301,7306,7311,7315,7320,7325,7330,7334,7339,7344,7349,7353,7358,7363,7368,7372,7377,7382],{"__ignoreMap":28},[33,7259,7260],{"class":35,"line":36},[33,7261,7262],{"class":64},"\u002F\u002F 1. createBrowserRouter + \u003CRouterProvider> is the recommended v6.4+ API —\n",[33,7264,7265],{"class":35,"line":54},[33,7266,7267],{"class":64},"\u002F\u002F    supports loaders, actions, errorElement, and automatic revalidation.\n",[33,7269,7270],{"class":35,"line":61},[33,7271,7272],{"class":64},"\u002F\u002F    The older \u003CBrowserRouter>\u002F\u003CRoutes> JSX API lacks these features.\n",[33,7274,7275],{"class":35,"line":68},[33,7276,58],{"emptyLinePlaceholder":57},[33,7278,7279],{"class":35,"line":74},[33,7280,7281],{"class":64},"\u002F\u002F 2. \u003CLink>\u002F\u003CNavLink> for direct-click navigation (preserves \u003Ca> affordances);\n",[33,7283,7284],{"class":35,"line":80},[33,7285,7286],{"class":64},"\u002F\u002F    useNavigate for imperative navigation after an action completes.\n",[33,7288,7289],{"class":35,"line":100},[33,7290,7291],{"class":64},"\u002F\u002F    Always use { replace: true } on auth-redirect \u003CNavigate> to avoid\n",[33,7293,7294],{"class":35,"line":118},[33,7295,7296],{"class":64},"\u002F\u002F    back-button redirect loops.\n",[33,7298,7299],{"class":35,"line":133},[33,7300,58],{"emptyLinePlaceholder":57},[33,7302,7303],{"class":35,"line":152},[33,7304,7305],{"class":64},"\u002F\u002F 3. Nested routes + \u003COutlet> = persistent layouts that don't remount\n",[33,7307,7308],{"class":35,"line":158},[33,7309,7310],{"class":64},"\u002F\u002F    between child navigations. State, scroll position, and UI persist.\n",[33,7312,7313],{"class":35,"line":163},[33,7314,58],{"emptyLinePlaceholder":57},[33,7316,7317],{"class":35,"line":175},[33,7318,7319],{"class":64},"\u002F\u002F 4. Loaders fetch data BEFORE the route renders — no loading flash,\n",[33,7321,7322],{"class":35,"line":195},[33,7323,7324],{"class":64},"\u002F\u002F    automatic abort via request.signal, and data ready via useLoaderData.\n",[33,7326,7327],{"class":35,"line":373},[33,7328,7329],{"class":64},"\u002F\u002F    Actions handle mutations and automatically revalidate active loaders.\n",[33,7331,7332],{"class":35,"line":384},[33,7333,58],{"emptyLinePlaceholder":57},[33,7335,7336],{"class":35,"line":419},[33,7337,7338],{"class":64},"\u002F\u002F 5. Protected routes need THREE auth states: loading, authenticated,\n",[33,7340,7341],{"class":35,"line":439},[33,7342,7343],{"class":64},"\u002F\u002F    unauthenticated. Two-state (user | null) flashes a redirect for\n",[33,7345,7346],{"class":35,"line":449},[33,7347,7348],{"class":64},"\u002F\u002F    every logged-in user during the initial async check.\n",[33,7350,7351],{"class":35,"line":455},[33,7352,58],{"emptyLinePlaceholder":57},[33,7354,7355],{"class":35,"line":460},[33,7356,7357],{"class":64},"\u002F\u002F 6. lazy() + Suspense at route boundaries = highest-leverage code split.\n",[33,7359,7360],{"class":35,"line":465},[33,7361,7362],{"class":64},"\u002F\u002F    The chunk download runs in parallel with the loader — data + code\n",[33,7364,7365],{"class":35,"line":471},[33,7366,7367],{"class":64},"\u002F\u002F    arrive together. Every lazy route needs a Suspense ancestor.\n",[33,7369,7370],{"class":35,"line":477},[33,7371,58],{"emptyLinePlaceholder":57},[33,7373,7374],{"class":35,"line":531},[33,7375,7376],{"class":64},"\u002F\u002F 7. useSearchParams for shareable\u002Frefresh-surviving view state (filters,\n",[33,7378,7379],{"class":35,"line":537},[33,7380,7381],{"class":64},"\u002F\u002F    sort, pagination). Debounce + { replace: true } for search-as-you-type\n",[33,7383,7384],{"class":35,"line":1100},[33,7385,7386],{"class":64},"\u002F\u002F    to avoid flooding browser history with per-keystroke entries.\n",[7388,7389,7390],"style",{},"html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .sk71V, html code.shiki .sk71V{--shiki-default:#22863A;--shiki-github-dark:#85E89D}",{"title":28,"searchDepth":54,"depth":54,"links":7392},[7393,7394,7395,7396,7397,7400,7401,7402,7403,7406,7407,7408,7409,7410,7411,7412],{"id":16,"depth":54,"text":17},{"id":201,"depth":54,"text":202},{"id":547,"depth":54,"text":548},{"id":853,"depth":54,"text":854},{"id":1271,"depth":54,"text":1272,"children":7398},[7399],{"id":1618,"depth":61,"text":1619},{"id":2033,"depth":54,"text":2034},{"id":2483,"depth":54,"text":2484},{"id":2788,"depth":54,"text":2789},{"id":3327,"depth":54,"text":3328,"children":7404},[7405],{"id":3806,"depth":61,"text":3807},{"id":3992,"depth":54,"text":3993},{"id":4721,"depth":54,"text":4722},{"id":6238,"depth":54,"text":6239},{"id":6643,"depth":54,"text":6644},{"id":6780,"depth":54,"text":6781},{"id":6954,"depth":54,"text":6955},{"id":7248,"depth":54,"text":7249},"React Router v6.4+ data-router API: createBrowserRouter, nested routes with Outlet, dynamic params, useNavigate, route protection, lazy-loaded routes with Suspense, useSearchParams, route loaders, actions, and error boundaries. Code-first reference for mid-to-senior React engineers.","md",{},"\u002Freact\u002F19-routing-react-router",{"title":5,"description":7413},"react\u002F19-routing-react-router","6uCibwsSAnndJzFYYqWaklj_dRfhJE0KnpqyTPX-3sE",1789924652464]