[{"data":1,"prerenderedAt":1728},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-dynamic-nested-routes-guide":3},{"id":4,"title":5,"body":6,"description":1713,"difficulty":1714,"extension":1715,"framework":1716,"frameworkSlug":1717,"meta":1718,"navigation":165,"order":125,"path":1719,"qaPath":1720,"seo":1721,"stem":1722,"subtopic":1723,"topic":1724,"topicSlug":1725,"updated":1726,"__hash__":1727},"blog\u002Fblog\u002Freact-dynamic-nested-routes-guide.md","React Router v6 Dynamic & Nested Routes — Complete Interview Guide",{"type":7,"value":8,"toc":1704},"minimark",[9,27,41,44,49,72,79,86,88,92,105,290,323,338,372,375,377,381,399,671,677,687,781,789,791,795,819,906,912,927,1036,1038,1042,1057,1253,1256,1305,1308,1310,1314,1337,1575,1594,1596,1600,1700],[10,11,12,13,17,18,22,23,26],"p",{},"React Router v6 rewrote the mental model for routing in React applications. Where v5 scattered ",[14,15,16],"code",{},"\u003CSwitch>"," blocks across your component tree, v6 centralises everything into a single, hierarchical route config that mirrors the actual URL hierarchy. Two features sit at the centre of nearly every interview question on the topic: ",[19,20,21],"strong",{},"dynamic segments"," — URL tokens that change per request — and ",[19,24,25],{},"nested routes"," — parent\u002Fchild relationships that let you compose layouts out of smaller pieces. Getting both right is what separates candidates who have skimmed the docs from those who have shipped production apps.",[10,28,29,30,33,34,33,37,40],{},"This guide walks through every concept interviewers test, with runnable code for each. By the end you will be able to explain ",[14,31,32],{},"useParams",", ",[14,35,36],{},"Outlet",[14,38,39],{},"useOutletContext",", index routes, splat routes, and the Data API loader pattern without hesitation.",[42,43],"hr",{},[45,46,48],"h2",{"id":47},"why-dynamic-and-nested-routes-matter","Why Dynamic and Nested Routes Matter",[10,50,51,52,55,56,59,60,63,64,67,68,71],{},"Real applications never have purely static URLs. A user profile lives at ",[14,53,54],{},"\u002Fusers\u002F42",", a blog post at ",[14,57,58],{},"\u002Fposts\u002Fmy-post-slug",", a repository at ",[14,61,62],{},"\u002Forgs\u002Facme\u002Frepos\u002Fdashboard",". Each of those variable tokens is a ",[19,65,66],{},"dynamic segment"," — a placeholder in the route pattern that React Router fills in at match time. Without them you would need a separate ",[14,69,70],{},"\u003CRoute>"," for every possible ID, which is obviously impossible.",[10,73,74,75,78],{},"Nested routes solve a different problem: shared UI. Almost every page in a product shares a navigation bar, a sidebar, or an authentication boundary. Duplicating that shell into every leaf component is brittle. Nested routes let you declare the shared wrapper once at a parent route and render child content inside a designated ",[14,76,77],{},"\u003COutlet \u002F>"," slot — the same pattern server-rendered frameworks like Next.js call \"layouts.\"",[10,80,81,82,85],{},"The two features combine constantly. A ",[14,83,84],{},"\u002Fdashboard\u002F:userId\u002Fsettings"," URL has a dynamic segment and lives inside at least two layout layers. Interviewers use exactly this kind of path to probe whether you understand both axes.",[42,87],{},[45,89,91],{"id":90},"dynamic-segments-and-useparams","Dynamic Segments and useParams",[10,93,94,95,98,99,104],{},"Define a dynamic segment by prefixing a path token with ",[14,96,97],{},":",". React Router captures whatever string occupies that position in the URL and makes it available via the ",[19,100,101],{},[14,102,103],{},"useParams()"," hook.",[106,107,112],"pre",{"className":108,"code":109,"language":110,"meta":111,"style":111},"language-jsx shiki shiki-themes github-light github-dark","\u002F\u002F Route definition\n\u003CRoute path=\"\u002Fusers\u002F:userId\u002Fposts\u002F:postId\" element={\u003CPostDetail \u002F>} \u002F>\n\n\u002F\u002F PostDetail.jsx\nimport { useParams } from 'react-router-dom';\n\nfunction PostDetail() {\n  const { userId, postId } = useParams();\n  \u002F\u002F Both values are strings — always. Coerce before comparing to numbers.\n  const numericPostId = Number(postId);\n\n  return \u003Cp>User {userId} — Post {numericPostId}\u003C\u002Fp>;\n}\n","jsx","",[14,113,114,123,160,167,173,191,196,208,236,242,259,264,284],{"__ignoreMap":111},[115,116,119],"span",{"class":117,"line":118},"line",1,[115,120,122],{"class":121},"sJ8bj","\u002F\u002F Route definition\n",[115,124,126,130,134,138,142,146,149,151,154,157],{"class":117,"line":125},2,[115,127,129],{"class":128},"sVt8B","\u003C",[115,131,133],{"class":132},"sj4cs","Route",[115,135,137],{"class":136},"sScJk"," path",[115,139,141],{"class":140},"szBVR","=",[115,143,145],{"class":144},"sZZnC","\"\u002Fusers\u002F:userId\u002Fposts\u002F:postId\"",[115,147,148],{"class":136}," element",[115,150,141],{"class":140},[115,152,153],{"class":128},"{\u003C",[115,155,156],{"class":132},"PostDetail",[115,158,159],{"class":128}," \u002F>} \u002F>\n",[115,161,163],{"class":117,"line":162},3,[115,164,166],{"emptyLinePlaceholder":165},true,"\n",[115,168,170],{"class":117,"line":169},4,[115,171,172],{"class":121},"\u002F\u002F PostDetail.jsx\n",[115,174,176,179,182,185,188],{"class":117,"line":175},5,[115,177,178],{"class":140},"import",[115,180,181],{"class":128}," { useParams } ",[115,183,184],{"class":140},"from",[115,186,187],{"class":144}," 'react-router-dom'",[115,189,190],{"class":128},";\n",[115,192,194],{"class":117,"line":193},6,[115,195,166],{"emptyLinePlaceholder":165},[115,197,199,202,205],{"class":117,"line":198},7,[115,200,201],{"class":140},"function",[115,203,204],{"class":136}," PostDetail",[115,206,207],{"class":128},"() {\n",[115,209,211,214,217,220,222,225,228,230,233],{"class":117,"line":210},8,[115,212,213],{"class":140},"  const",[115,215,216],{"class":128}," { ",[115,218,219],{"class":132},"userId",[115,221,33],{"class":128},[115,223,224],{"class":132},"postId",[115,226,227],{"class":128}," } ",[115,229,141],{"class":140},[115,231,232],{"class":136}," useParams",[115,234,235],{"class":128},"();\n",[115,237,239],{"class":117,"line":238},9,[115,240,241],{"class":121},"  \u002F\u002F Both values are strings — always. Coerce before comparing to numbers.\n",[115,243,245,247,250,253,256],{"class":117,"line":244},10,[115,246,213],{"class":140},[115,248,249],{"class":132}," numericPostId",[115,251,252],{"class":140}," =",[115,254,255],{"class":136}," Number",[115,257,258],{"class":128},"(postId);\n",[115,260,262],{"class":117,"line":261},11,[115,263,166],{"emptyLinePlaceholder":165},[115,265,267,270,273,276,279,281],{"class":117,"line":266},12,[115,268,269],{"class":140},"  return",[115,271,272],{"class":128}," \u003C",[115,274,10],{"class":275},"s9eBZ",[115,277,278],{"class":128},">User {userId} — Post {numericPostId}\u003C\u002F",[115,280,10],{"class":275},[115,282,283],{"class":128},">;\n",[115,285,287],{"class":117,"line":286},13,[115,288,289],{"class":128},"}\n",[10,291,292,293,296,297,299,300,303,304,307,308,311,312,315,316,33,319,322],{},"The critical interview trap here: ",[19,294,295],{},"params are always strings",". ",[14,298,103],{}," returns ",[14,301,302],{},"{ userId: \"42\", postId: \"7\" }",", not numbers. A strict equality check like ",[14,305,306],{},"posts.find(p => p.id === postId)"," silently returns ",[14,309,310],{},"undefined"," because ",[14,313,314],{},"7 !== \"7\""," in JavaScript. Coerce at the top of the component — ",[14,317,318],{},"Number(id)",[14,320,321],{},"parseInt(id, 10)",", or a Zod parse — before any data lookup.",[10,324,325,326,329,330,333,334,337],{},"A ",[19,327,328],{},"splat route"," extends this idea to catch arbitrary path suffixes. The pattern ",[14,331,332],{},"path=\"*\""," matches everything beyond that point and stores it in ",[14,335,336],{},"params[\"*\"]",". It is most useful at the end of a route tree for custom 404 pages:",[106,339,341],{"className":108,"code":340,"language":110,"meta":111,"style":111},"\u002F\u002F Catches any URL that nothing else matched\n\u003CRoute path=\"*\" element={\u003CNotFound \u002F>} \u002F>\n",[14,342,343,348],{"__ignoreMap":111},[115,344,345],{"class":117,"line":118},[115,346,347],{"class":121},"\u002F\u002F Catches any URL that nothing else matched\n",[115,349,350,352,354,356,358,361,363,365,367,370],{"class":117,"line":125},[115,351,129],{"class":128},[115,353,133],{"class":132},[115,355,137],{"class":136},[115,357,141],{"class":140},[115,359,360],{"class":144},"\"*\"",[115,362,148],{"class":136},[115,364,141],{"class":140},[115,366,153],{"class":128},[115,368,369],{"class":132},"NotFound",[115,371,159],{"class":128},[10,373,374],{},"React Router matches routes in definition order, so place your splat last.",[42,376],{},[45,378,380],{"id":379},"nested-routes-outlet-and-layout-routes","Nested Routes, Outlet, and Layout Routes",[10,382,383,384,386,387,389,390,393,394,398],{},"Nesting routes means placing ",[14,385,70],{}," elements as children of other ",[14,388,70],{}," elements. The parent's ",[14,391,392],{},"element"," renders unconditionally whenever any of its children match; the child's element renders inside the parent via ",[19,395,396],{},[14,397,77],{},".",[106,400,402],{"className":108,"code":401,"language":110,"meta":111,"style":111},"\u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboardLayout \u002F>}>\n  \u003CRoute index element={\u003COverview \u002F>} \u002F>\n  \u003CRoute path=\"settings\" element={\u003CSettings \u002F>} \u002F>\n\u003C\u002FRoute>\n\n\u002F\u002F DashboardLayout.jsx\nimport { Outlet, NavLink } from 'react-router-dom';\n\nfunction DashboardLayout() {\n  return (\n    \u003Cdiv className=\"dashboard\">\n      \u003Caside>\n        \u003CNavLink to=\"\u002Fdashboard\" end>Overview\u003C\u002FNavLink>\n        \u003CNavLink to=\"\u002Fdashboard\u002Fsettings\">Settings\u003C\u002FNavLink>\n      \u003C\u002Faside>\n      {\u002F* child route element renders here *\u002F}\n      \u003Cmain>\n        \u003COutlet \u002F>\n      \u003C\u002Fmain>\n    \u003C\u002Fdiv>\n  );\n}\n",[14,403,404,429,450,474,484,488,493,506,510,519,526,544,554,579,600,610,621,631,641,650,660,666],{"__ignoreMap":111},[115,405,406,408,410,412,414,417,419,421,423,426],{"class":117,"line":118},[115,407,129],{"class":128},[115,409,133],{"class":132},[115,411,137],{"class":136},[115,413,141],{"class":140},[115,415,416],{"class":144},"\"\u002Fdashboard\"",[115,418,148],{"class":136},[115,420,141],{"class":140},[115,422,153],{"class":128},[115,424,425],{"class":132},"DashboardLayout",[115,427,428],{"class":128}," \u002F>}>\n",[115,430,431,434,436,439,441,443,445,448],{"class":117,"line":125},[115,432,433],{"class":128},"  \u003C",[115,435,133],{"class":132},[115,437,438],{"class":136}," index",[115,440,148],{"class":136},[115,442,141],{"class":140},[115,444,153],{"class":128},[115,446,447],{"class":132},"Overview",[115,449,159],{"class":128},[115,451,452,454,456,458,460,463,465,467,469,472],{"class":117,"line":162},[115,453,433],{"class":128},[115,455,133],{"class":132},[115,457,137],{"class":136},[115,459,141],{"class":140},[115,461,462],{"class":144},"\"settings\"",[115,464,148],{"class":136},[115,466,141],{"class":140},[115,468,153],{"class":128},[115,470,471],{"class":132},"Settings",[115,473,159],{"class":128},[115,475,476,479,481],{"class":117,"line":169},[115,477,478],{"class":128},"\u003C\u002F",[115,480,133],{"class":132},[115,482,483],{"class":128},">\n",[115,485,486],{"class":117,"line":175},[115,487,166],{"emptyLinePlaceholder":165},[115,489,490],{"class":117,"line":193},[115,491,492],{"class":121},"\u002F\u002F DashboardLayout.jsx\n",[115,494,495,497,500,502,504],{"class":117,"line":198},[115,496,178],{"class":140},[115,498,499],{"class":128}," { Outlet, NavLink } ",[115,501,184],{"class":140},[115,503,187],{"class":144},[115,505,190],{"class":128},[115,507,508],{"class":117,"line":210},[115,509,166],{"emptyLinePlaceholder":165},[115,511,512,514,517],{"class":117,"line":238},[115,513,201],{"class":140},[115,515,516],{"class":136}," DashboardLayout",[115,518,207],{"class":128},[115,520,521,523],{"class":117,"line":244},[115,522,269],{"class":140},[115,524,525],{"class":128}," (\n",[115,527,528,531,534,537,539,542],{"class":117,"line":261},[115,529,530],{"class":128},"    \u003C",[115,532,533],{"class":275},"div",[115,535,536],{"class":136}," className",[115,538,141],{"class":140},[115,540,541],{"class":144},"\"dashboard\"",[115,543,483],{"class":128},[115,545,546,549,552],{"class":117,"line":266},[115,547,548],{"class":128},"      \u003C",[115,550,551],{"class":275},"aside",[115,553,483],{"class":128},[115,555,556,559,562,565,567,569,572,575,577],{"class":117,"line":286},[115,557,558],{"class":128},"        \u003C",[115,560,561],{"class":132},"NavLink",[115,563,564],{"class":136}," to",[115,566,141],{"class":140},[115,568,416],{"class":144},[115,570,571],{"class":136}," end",[115,573,574],{"class":128},">Overview\u003C\u002F",[115,576,561],{"class":132},[115,578,483],{"class":128},[115,580,582,584,586,588,590,593,596,598],{"class":117,"line":581},14,[115,583,558],{"class":128},[115,585,561],{"class":132},[115,587,564],{"class":136},[115,589,141],{"class":140},[115,591,592],{"class":144},"\"\u002Fdashboard\u002Fsettings\"",[115,594,595],{"class":128},">Settings\u003C\u002F",[115,597,561],{"class":132},[115,599,483],{"class":128},[115,601,603,606,608],{"class":117,"line":602},15,[115,604,605],{"class":128},"      \u003C\u002F",[115,607,551],{"class":275},[115,609,483],{"class":128},[115,611,613,616,619],{"class":117,"line":612},16,[115,614,615],{"class":128},"      {",[115,617,618],{"class":121},"\u002F* child route element renders here *\u002F",[115,620,289],{"class":128},[115,622,624,626,629],{"class":117,"line":623},17,[115,625,548],{"class":128},[115,627,628],{"class":275},"main",[115,630,483],{"class":128},[115,632,634,636,638],{"class":117,"line":633},18,[115,635,558],{"class":128},[115,637,36],{"class":132},[115,639,640],{"class":128}," \u002F>\n",[115,642,644,646,648],{"class":117,"line":643},19,[115,645,605],{"class":128},[115,647,628],{"class":275},[115,649,483],{"class":128},[115,651,653,656,658],{"class":117,"line":652},20,[115,654,655],{"class":128},"    \u003C\u002F",[115,657,533],{"class":275},[115,659,483],{"class":128},[115,661,663],{"class":117,"line":662},21,[115,664,665],{"class":128},"  );\n",[115,667,669],{"class":117,"line":668},22,[115,670,289],{"class":128},[10,672,673,674,676],{},"Forgetting ",[14,675,77],{}," is the single most common nested-route bug — the URL changes, no error is thrown, but the child simply never appears on screen.",[10,678,325,679,682,683,686],{},[19,680,681],{},"layout route"," is a route that exists purely to provide shared UI, with no URL segment of its own. You achieve this by omitting ",[14,684,685],{},"path"," from the route definition. Using the object-config style:",[106,688,690],{"className":108,"code":689,"language":110,"meta":111,"style":111},"const router = createBrowserRouter([\n  {\n    \u002F\u002F no \"path\" — this is a layout-only route\n    element: \u003CAppShell \u002F>,\n    children: [\n      { path: '\u002F',      element: \u003CHome \u002F> },\n      { path: '\u002Fabout', element: \u003CAbout \u002F> },\n    ],\n  },\n]);\n",[14,691,692,708,713,718,729,734,751,766,771,776],{"__ignoreMap":111},[115,693,694,697,700,702,705],{"class":117,"line":118},[115,695,696],{"class":140},"const",[115,698,699],{"class":132}," router",[115,701,252],{"class":140},[115,703,704],{"class":136}," createBrowserRouter",[115,706,707],{"class":128},"([\n",[115,709,710],{"class":117,"line":125},[115,711,712],{"class":128},"  {\n",[115,714,715],{"class":117,"line":162},[115,716,717],{"class":121},"    \u002F\u002F no \"path\" — this is a layout-only route\n",[115,719,720,723,726],{"class":117,"line":169},[115,721,722],{"class":128},"    element: \u003C",[115,724,725],{"class":132},"AppShell",[115,727,728],{"class":128}," \u002F>,\n",[115,730,731],{"class":117,"line":175},[115,732,733],{"class":128},"    children: [\n",[115,735,736,739,742,745,748],{"class":117,"line":193},[115,737,738],{"class":128},"      { path: ",[115,740,741],{"class":144},"'\u002F'",[115,743,744],{"class":128},",      element: \u003C",[115,746,747],{"class":132},"Home",[115,749,750],{"class":128}," \u002F> },\n",[115,752,753,755,758,761,764],{"class":117,"line":198},[115,754,738],{"class":128},[115,756,757],{"class":144},"'\u002Fabout'",[115,759,760],{"class":128},", element: \u003C",[115,762,763],{"class":132},"About",[115,765,750],{"class":128},[115,767,768],{"class":117,"line":210},[115,769,770],{"class":128},"    ],\n",[115,772,773],{"class":117,"line":238},[115,774,775],{"class":128},"  },\n",[115,777,778],{"class":117,"line":244},[115,779,780],{"class":128},"]);\n",[10,782,783,785,786,788],{},[14,784,725],{}," renders its ",[14,787,77],{}," and the children match their own paths directly. No URL segment is consumed by the layout.",[42,790],{},[45,792,794],{"id":793},"index-routes-and-catch-all-routes-in-nested-layouts","Index Routes and Catch-All Routes in Nested Layouts",[10,796,797,798,801,802,804,805,807,808,811,812,815,816,818],{},"When a user navigates to ",[14,799,800],{},"\u002Fdashboard"," exactly, React Router renders ",[14,803,425],{}," but finds no matching child route — so ",[14,806,77],{}," renders nothing, leaving a blank content area. The fix is an ",[19,809,810],{},"index route",": a child with ",[14,813,814],{},"index"," instead of ",[14,817,685],{},", which renders at the parent's exact URL.",[106,820,822],{"className":108,"code":821,"language":110,"meta":111,"style":111},"\u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboardLayout \u002F>}>\n  {\u002F* renders at \u002Fdashboard exactly *\u002F}\n  \u003CRoute index element={\u003COverview \u002F>} \u002F>\n  \u003CRoute path=\"analytics\" element={\u003CAnalytics \u002F>} \u002F>\n\u003C\u002FRoute>\n",[14,823,824,846,856,874,898],{"__ignoreMap":111},[115,825,826,828,830,832,834,836,838,840,842,844],{"class":117,"line":118},[115,827,129],{"class":128},[115,829,133],{"class":132},[115,831,137],{"class":136},[115,833,141],{"class":140},[115,835,416],{"class":144},[115,837,148],{"class":136},[115,839,141],{"class":140},[115,841,153],{"class":128},[115,843,425],{"class":132},[115,845,428],{"class":128},[115,847,848,851,854],{"class":117,"line":125},[115,849,850],{"class":128},"  {",[115,852,853],{"class":121},"\u002F* renders at \u002Fdashboard exactly *\u002F",[115,855,289],{"class":128},[115,857,858,860,862,864,866,868,870,872],{"class":117,"line":162},[115,859,433],{"class":128},[115,861,133],{"class":132},[115,863,438],{"class":136},[115,865,148],{"class":136},[115,867,141],{"class":140},[115,869,153],{"class":128},[115,871,447],{"class":132},[115,873,159],{"class":128},[115,875,876,878,880,882,884,887,889,891,893,896],{"class":117,"line":169},[115,877,433],{"class":128},[115,879,133],{"class":132},[115,881,137],{"class":136},[115,883,141],{"class":140},[115,885,886],{"class":144},"\"analytics\"",[115,888,148],{"class":136},[115,890,141],{"class":140},[115,892,153],{"class":128},[115,894,895],{"class":132},"Analytics",[115,897,159],{"class":128},[115,899,900,902,904],{"class":117,"line":175},[115,901,478],{"class":128},[115,903,133],{"class":132},[115,905,483],{"class":128},[10,907,908,909,911],{},"Every layout that owns an ",[14,910,77],{}," should have an index route. Without it you are shipping a partial blank page that will confuse users and puzzle your future self.",[10,913,914,915,918,919,921,922,926],{},"The counterpart is a ",[19,916,917],{},"nested catch-all"," — a ",[14,920,332],{}," child that renders a 404 ",[923,924,925],"em",{},"within"," the layout. This matters because a root-level splat renders outside all layouts, stripping navigation away from the 404 page. A nested splat keeps the layout visible:",[106,928,930],{"className":108,"code":929,"language":110,"meta":111,"style":111},"\u003CRoute path=\"\u002Fapp\" element={\u003CAppLayout \u002F>}>\n  \u003CRoute index element={\u003CHome \u002F>} \u002F>\n  \u003CRoute path=\"posts\u002F:id\" element={\u003CPostDetail \u002F>} \u002F>\n  {\u002F* 404 inside the layout — navigation stays visible *\u002F}\n  \u003CRoute path=\"*\" element={\u003CNotFound \u002F>} \u002F>\n\u003C\u002FRoute>\n",[14,931,932,956,974,997,1006,1028],{"__ignoreMap":111},[115,933,934,936,938,940,942,945,947,949,951,954],{"class":117,"line":118},[115,935,129],{"class":128},[115,937,133],{"class":132},[115,939,137],{"class":136},[115,941,141],{"class":140},[115,943,944],{"class":144},"\"\u002Fapp\"",[115,946,148],{"class":136},[115,948,141],{"class":140},[115,950,153],{"class":128},[115,952,953],{"class":132},"AppLayout",[115,955,428],{"class":128},[115,957,958,960,962,964,966,968,970,972],{"class":117,"line":125},[115,959,433],{"class":128},[115,961,133],{"class":132},[115,963,438],{"class":136},[115,965,148],{"class":136},[115,967,141],{"class":140},[115,969,153],{"class":128},[115,971,747],{"class":132},[115,973,159],{"class":128},[115,975,976,978,980,982,984,987,989,991,993,995],{"class":117,"line":162},[115,977,433],{"class":128},[115,979,133],{"class":132},[115,981,137],{"class":136},[115,983,141],{"class":140},[115,985,986],{"class":144},"\"posts\u002F:id\"",[115,988,148],{"class":136},[115,990,141],{"class":140},[115,992,153],{"class":128},[115,994,156],{"class":132},[115,996,159],{"class":128},[115,998,999,1001,1004],{"class":117,"line":169},[115,1000,850],{"class":128},[115,1002,1003],{"class":121},"\u002F* 404 inside the layout — navigation stays visible *\u002F",[115,1005,289],{"class":128},[115,1007,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026],{"class":117,"line":175},[115,1009,433],{"class":128},[115,1011,133],{"class":132},[115,1013,137],{"class":136},[115,1015,141],{"class":140},[115,1017,360],{"class":144},[115,1019,148],{"class":136},[115,1021,141],{"class":140},[115,1023,153],{"class":128},[115,1025,369],{"class":132},[115,1027,159],{"class":128},[115,1029,1030,1032,1034],{"class":117,"line":193},[115,1031,478],{"class":128},[115,1033,133],{"class":132},[115,1035,483],{"class":128},[42,1037],{},[45,1039,1041],{"id":1040},"sharing-data-with-useoutletcontext","Sharing Data with useOutletContext",[10,1043,1044,1045,1050,1051,1056],{},"Sometimes the parent layout fetches data — a logged-in user object, a workspace — that child routes need. Threading it down via props is impossible because React Router, not your code, instantiates the child element. The solution is ",[19,1046,1047],{},[14,1048,1049],{},"\u003COutlet context={...} \u002F>"," in the parent and ",[19,1052,1053],{},[14,1054,1055],{},"useOutletContext()"," in the child.",[106,1058,1060],{"className":108,"code":1059,"language":110,"meta":111,"style":111},"\u002F\u002F DashboardLayout.jsx\nimport { Outlet } from 'react-router-dom';\n\nfunction DashboardLayout() {\n  const user = useCurrentUser(); \u002F\u002F fetch or read from store\n\n  return (\n    \u003Cdiv>\n      \u003CDashboardNav user={user} \u002F>\n      {\u002F* pass user to every child route *\u002F}\n      \u003COutlet context={{ user }} \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F ProfilePage.jsx\nimport { useOutletContext } from 'react-router-dom';\n\nfunction ProfilePage() {\n  const { user } = useOutletContext();\n  return \u003Ch1>Welcome, {user.name}\u003C\u002Fh1>;\n}\n",[14,1061,1062,1066,1079,1083,1091,1109,1113,1119,1127,1141,1150,1164,1172,1176,1180,1184,1189,1202,1206,1215,1233,1249],{"__ignoreMap":111},[115,1063,1064],{"class":117,"line":118},[115,1065,492],{"class":121},[115,1067,1068,1070,1073,1075,1077],{"class":117,"line":125},[115,1069,178],{"class":140},[115,1071,1072],{"class":128}," { Outlet } ",[115,1074,184],{"class":140},[115,1076,187],{"class":144},[115,1078,190],{"class":128},[115,1080,1081],{"class":117,"line":162},[115,1082,166],{"emptyLinePlaceholder":165},[115,1084,1085,1087,1089],{"class":117,"line":169},[115,1086,201],{"class":140},[115,1088,516],{"class":136},[115,1090,207],{"class":128},[115,1092,1093,1095,1098,1100,1103,1106],{"class":117,"line":175},[115,1094,213],{"class":140},[115,1096,1097],{"class":132}," user",[115,1099,252],{"class":140},[115,1101,1102],{"class":136}," useCurrentUser",[115,1104,1105],{"class":128},"(); ",[115,1107,1108],{"class":121},"\u002F\u002F fetch or read from store\n",[115,1110,1111],{"class":117,"line":193},[115,1112,166],{"emptyLinePlaceholder":165},[115,1114,1115,1117],{"class":117,"line":198},[115,1116,269],{"class":140},[115,1118,525],{"class":128},[115,1120,1121,1123,1125],{"class":117,"line":210},[115,1122,530],{"class":128},[115,1124,533],{"class":275},[115,1126,483],{"class":128},[115,1128,1129,1131,1134,1136,1138],{"class":117,"line":238},[115,1130,548],{"class":128},[115,1132,1133],{"class":132},"DashboardNav",[115,1135,1097],{"class":136},[115,1137,141],{"class":140},[115,1139,1140],{"class":128},"{user} \u002F>\n",[115,1142,1143,1145,1148],{"class":117,"line":244},[115,1144,615],{"class":128},[115,1146,1147],{"class":121},"\u002F* pass user to every child route *\u002F",[115,1149,289],{"class":128},[115,1151,1152,1154,1156,1159,1161],{"class":117,"line":261},[115,1153,548],{"class":128},[115,1155,36],{"class":132},[115,1157,1158],{"class":136}," context",[115,1160,141],{"class":140},[115,1162,1163],{"class":128},"{{ user }} \u002F>\n",[115,1165,1166,1168,1170],{"class":117,"line":266},[115,1167,655],{"class":128},[115,1169,533],{"class":275},[115,1171,483],{"class":128},[115,1173,1174],{"class":117,"line":286},[115,1175,665],{"class":128},[115,1177,1178],{"class":117,"line":581},[115,1179,289],{"class":128},[115,1181,1182],{"class":117,"line":602},[115,1183,166],{"emptyLinePlaceholder":165},[115,1185,1186],{"class":117,"line":612},[115,1187,1188],{"class":121},"\u002F\u002F ProfilePage.jsx\n",[115,1190,1191,1193,1196,1198,1200],{"class":117,"line":623},[115,1192,178],{"class":140},[115,1194,1195],{"class":128}," { useOutletContext } ",[115,1197,184],{"class":140},[115,1199,187],{"class":144},[115,1201,190],{"class":128},[115,1203,1204],{"class":117,"line":633},[115,1205,166],{"emptyLinePlaceholder":165},[115,1207,1208,1210,1213],{"class":117,"line":643},[115,1209,201],{"class":140},[115,1211,1212],{"class":136}," ProfilePage",[115,1214,207],{"class":128},[115,1216,1217,1219,1221,1224,1226,1228,1231],{"class":117,"line":652},[115,1218,213],{"class":140},[115,1220,216],{"class":128},[115,1222,1223],{"class":132},"user",[115,1225,227],{"class":128},[115,1227,141],{"class":140},[115,1229,1230],{"class":136}," useOutletContext",[115,1232,235],{"class":128},[115,1234,1235,1237,1239,1242,1245,1247],{"class":117,"line":662},[115,1236,269],{"class":140},[115,1238,272],{"class":128},[115,1240,1241],{"class":275},"h1",[115,1243,1244],{"class":128},">Welcome, {user.name}\u003C\u002F",[115,1246,1241],{"class":275},[115,1248,283],{"class":128},[115,1250,1251],{"class":117,"line":668},[115,1252,289],{"class":128},[10,1254,1255],{},"In TypeScript, create a typed wrapper hook and export it from the layout file:",[106,1257,1261],{"className":1258,"code":1259,"language":1260,"meta":111,"style":111},"language-tsx shiki shiki-themes github-light github-dark","\u002F\u002F Export a typed hook — children import this, not useOutletContext directly\nexport function useDashboardCtx() {\n  return useOutletContext\u003C{ user: User }>();\n}\n","tsx",[14,1262,1263,1268,1281,1301],{"__ignoreMap":111},[115,1264,1265],{"class":117,"line":118},[115,1266,1267],{"class":121},"\u002F\u002F Export a typed hook — children import this, not useOutletContext directly\n",[115,1269,1270,1273,1276,1279],{"class":117,"line":125},[115,1271,1272],{"class":140},"export",[115,1274,1275],{"class":140}," function",[115,1277,1278],{"class":136}," useDashboardCtx",[115,1280,207],{"class":128},[115,1282,1283,1285,1287,1290,1293,1295,1298],{"class":117,"line":162},[115,1284,269],{"class":140},[115,1286,1230],{"class":136},[115,1288,1289],{"class":128},"\u003C{ ",[115,1291,1223],{"class":1292},"s4XuR",[115,1294,97],{"class":140},[115,1296,1297],{"class":136}," User",[115,1299,1300],{"class":128}," }>();\n",[115,1302,1303],{"class":117,"line":169},[115,1304,289],{"class":128},[10,1306,1307],{},"This gives full IDE autocomplete in child routes and catches shape mismatches at compile time.",[42,1309],{},[45,1311,1313],{"id":1312},"data-api-loaders-react-router-v64","Data API Loaders (React Router v6.4+)",[10,1315,1316,1317,1320,1321,1324,1325,1328,1329,1332,1333,1336],{},"The ",[19,1318,1319],{},"Data API",", introduced in v6.4, attaches async ",[14,1322,1323],{},"loader"," functions to routes in the object config. React Router calls them before the component renders, passing ",[14,1326,1327],{},"{ params, request }",". The component reads the resolved data via ",[14,1330,1331],{},"useLoaderData()"," — no ",[14,1334,1335],{},"useEffect",", no loading spinner, no manual error state.",[106,1338,1340],{"className":108,"code":1339,"language":110,"meta":111,"style":111},"\u002F\u002F Loader function — runs before the component\nasync function postLoader({ params }) {\n  const res = await fetch(`\u002Fapi\u002Fposts\u002F${params.postId}`);\n  if (!res.ok) throw new Response('Not Found', { status: 404 });\n  return res.json(); \u002F\u002F this becomes the loader data\n}\n\n\u002F\u002F Route definition\nconst router = createBrowserRouter([\n  {\n    path: '\u002Fposts\u002F:postId',\n    element: \u003CPostDetail \u002F>,\n    loader: postLoader,\n    errorElement: \u003CPostError \u002F>, \u002F\u002F renders when loader throws\n  },\n]);\n\n\u002F\u002F Component — data is already resolved\nfunction PostDetail() {\n  const post = useLoaderData();\n  return \u003Ch1>{post.title}\u003C\u002Fh1>;\n}\n",[14,1341,1342,1347,1366,1399,1436,1451,1455,1459,1463,1475,1479,1490,1498,1503,1517,1521,1525,1529,1534,1542,1556,1571],{"__ignoreMap":111},[115,1343,1344],{"class":117,"line":118},[115,1345,1346],{"class":121},"\u002F\u002F Loader function — runs before the component\n",[115,1348,1349,1352,1354,1357,1360,1363],{"class":117,"line":125},[115,1350,1351],{"class":140},"async",[115,1353,1275],{"class":140},[115,1355,1356],{"class":136}," postLoader",[115,1358,1359],{"class":128},"({ ",[115,1361,1362],{"class":1292},"params",[115,1364,1365],{"class":128}," }) {\n",[115,1367,1368,1370,1373,1375,1378,1381,1384,1387,1389,1391,1393,1396],{"class":117,"line":162},[115,1369,213],{"class":140},[115,1371,1372],{"class":132}," res",[115,1374,252],{"class":140},[115,1376,1377],{"class":140}," await",[115,1379,1380],{"class":136}," fetch",[115,1382,1383],{"class":128},"(",[115,1385,1386],{"class":144},"`\u002Fapi\u002Fposts\u002F${",[115,1388,1362],{"class":128},[115,1390,398],{"class":144},[115,1392,224],{"class":128},[115,1394,1395],{"class":144},"}`",[115,1397,1398],{"class":128},");\n",[115,1400,1401,1404,1407,1410,1413,1416,1419,1422,1424,1427,1430,1433],{"class":117,"line":169},[115,1402,1403],{"class":140},"  if",[115,1405,1406],{"class":128}," (",[115,1408,1409],{"class":140},"!",[115,1411,1412],{"class":128},"res.ok) ",[115,1414,1415],{"class":140},"throw",[115,1417,1418],{"class":140}," new",[115,1420,1421],{"class":136}," Response",[115,1423,1383],{"class":128},[115,1425,1426],{"class":144},"'Not Found'",[115,1428,1429],{"class":128},", { status: ",[115,1431,1432],{"class":132},"404",[115,1434,1435],{"class":128}," });\n",[115,1437,1438,1440,1443,1446,1448],{"class":117,"line":175},[115,1439,269],{"class":140},[115,1441,1442],{"class":128}," res.",[115,1444,1445],{"class":136},"json",[115,1447,1105],{"class":128},[115,1449,1450],{"class":121},"\u002F\u002F this becomes the loader data\n",[115,1452,1453],{"class":117,"line":193},[115,1454,289],{"class":128},[115,1456,1457],{"class":117,"line":198},[115,1458,166],{"emptyLinePlaceholder":165},[115,1460,1461],{"class":117,"line":210},[115,1462,122],{"class":121},[115,1464,1465,1467,1469,1471,1473],{"class":117,"line":238},[115,1466,696],{"class":140},[115,1468,699],{"class":132},[115,1470,252],{"class":140},[115,1472,704],{"class":136},[115,1474,707],{"class":128},[115,1476,1477],{"class":117,"line":244},[115,1478,712],{"class":128},[115,1480,1481,1484,1487],{"class":117,"line":261},[115,1482,1483],{"class":128},"    path: ",[115,1485,1486],{"class":144},"'\u002Fposts\u002F:postId'",[115,1488,1489],{"class":128},",\n",[115,1491,1492,1494,1496],{"class":117,"line":266},[115,1493,722],{"class":128},[115,1495,156],{"class":132},[115,1497,728],{"class":128},[115,1499,1500],{"class":117,"line":286},[115,1501,1502],{"class":128},"    loader: postLoader,\n",[115,1504,1505,1508,1511,1514],{"class":117,"line":581},[115,1506,1507],{"class":128},"    errorElement: \u003C",[115,1509,1510],{"class":132},"PostError",[115,1512,1513],{"class":128}," \u002F>, ",[115,1515,1516],{"class":121},"\u002F\u002F renders when loader throws\n",[115,1518,1519],{"class":117,"line":602},[115,1520,775],{"class":128},[115,1522,1523],{"class":117,"line":612},[115,1524,780],{"class":128},[115,1526,1527],{"class":117,"line":623},[115,1528,166],{"emptyLinePlaceholder":165},[115,1530,1531],{"class":117,"line":633},[115,1532,1533],{"class":121},"\u002F\u002F Component — data is already resolved\n",[115,1535,1536,1538,1540],{"class":117,"line":643},[115,1537,201],{"class":140},[115,1539,204],{"class":136},[115,1541,207],{"class":128},[115,1543,1544,1546,1549,1551,1554],{"class":117,"line":652},[115,1545,213],{"class":140},[115,1547,1548],{"class":132}," post",[115,1550,252],{"class":140},[115,1552,1553],{"class":136}," useLoaderData",[115,1555,235],{"class":128},[115,1557,1558,1560,1562,1564,1567,1569],{"class":117,"line":662},[115,1559,269],{"class":140},[115,1561,272],{"class":128},[115,1563,1241],{"class":275},[115,1565,1566],{"class":128},">{post.title}\u003C\u002F",[115,1568,1241],{"class":275},[115,1570,283],{"class":128},[115,1572,1573],{"class":117,"line":668},[115,1574,289],{"class":128},[10,1576,1577,1578,1581,1582,1585,1586,1589,1590,1593],{},"Loaders are also the right place to validate params. If ",[14,1579,1580],{},":postId"," is not a valid integer, throw ",[14,1583,1584],{},"redirect('\u002F404')"," from the loader before the component ever mounts. The Data API requires ",[14,1587,1588],{},"createBrowserRouter"," — the older JSX ",[14,1591,1592],{},"\u003CRoutes>"," approach does not support loaders or actions.",[42,1595],{},[45,1597,1599],{"id":1598},"common-interview-questions-at-a-glance","Common Interview Questions at a Glance",[1601,1602,1603,1617,1626,1637,1649,1658,1669,1688],"ul",{},[1604,1605,1606,1612,1613,1616],"li",{},[19,1607,1608,1609,1611],{},"What type does ",[14,1610,103],{}," return?"," Always ",[14,1614,1615],{},"Record\u003Cstring, string>"," — coerce numbers before comparing.",[1604,1618,1619,1625],{},[19,1620,1621,1622,1624],{},"What happens if a parent route doesn't render ",[14,1623,77],{},"?"," Child routes match but render nowhere — silent blank content area.",[1604,1627,1628,1631,1632,815,1634,1636],{},[19,1629,1630],{},"What is an index route?"," A child with ",[14,1633,814],{},[14,1635,685],{},"; renders at the parent's exact URL as the default child.",[1604,1638,1639,1642,1643,1646,1647,1056],{},[19,1640,1641],{},"How do you pass data from a layout to its child routes?"," ",[14,1644,1645],{},"\u003COutlet context={value} \u002F>"," in the parent, ",[14,1648,1055],{},[1604,1650,1651,1654,1655,1657],{},[19,1652,1653],{},"What is a layout route?"," A route with an element but no ",[14,1656,685],{},"; provides shared UI without consuming a URL segment.",[1604,1659,1660,1668],{},[19,1661,1662,1663,815,1666,1624],{},"When do you use ",[14,1664,1665],{},"useSearchParams",[14,1667,32],{}," Route params for identity (which resource); search params for display state (sort order, pagination, filters).",[1604,1670,1671,1680,1681,33,1684,1687],{},[19,1672,1673,1674,1676,1677,1679],{},"What does ",[14,1675,1588],{}," unlock that ",[14,1678,1592],{}," cannot do?"," Loaders, actions, ",[14,1682,1683],{},"errorElement",[14,1685,1686],{},"defer",", and form-submission handling via the Data API.",[1604,1689,1690,1696,1697,1699],{},[19,1691,1692,1693,1695],{},"How does ",[14,1694,1683],{}," bubble?"," React Router walks up the route tree to the nearest ancestor with ",[14,1698,1683],{}," and renders it there, preserving all higher layouts.",[1701,1702,1703],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":111,"searchDepth":125,"depth":125,"links":1705},[1706,1707,1708,1709,1710,1711,1712],{"id":47,"depth":125,"text":48},{"id":90,"depth":125,"text":91},{"id":379,"depth":125,"text":380},{"id":793,"depth":125,"text":794},{"id":1040,"depth":125,"text":1041},{"id":1312,"depth":125,"text":1313},{"id":1598,"depth":125,"text":1599},"Master React Router v6 dynamic and nested routes for interviews — useParams, Outlet, layout routes, index routes, useOutletContext, and the data API with loaders.","medium","md","React","react",{},"\u002Fblog\u002Freact-dynamic-nested-routes-guide","\u002Freact\u002Frouting\u002Fdynamic-nested-routes",{"title":5,"description":1713},"blog\u002Freact-dynamic-nested-routes-guide","Dynamic and Nested Routes","Routing","routing","2026-06-24","WOscwaKLajHWYNJ0PhcVyrIG2uGZ3Xi1BthAVvdeYhQ",1782244083220]