[{"data":1,"prerenderedAt":131},["ShallowReactive",2],{"qa-\u002Freact\u002Frouting\u002Fdynamic-nested-routes":3},{"page":4,"siblings":114,"blog":128},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":12,"path":20,"questions":21,"questionsCount":104,"related":105,"seo":106,"seoDescription":107,"stem":108,"subtopic":109,"topic":110,"topicSlug":111,"updated":112,"__hash__":113},"qa\u002Freact\u002Frouting\u002Fdynamic-nested-routes.md","Dynamic Nested Routes",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","React","react",{},true,"\u002Freact\u002Frouting\u002Fdynamic-nested-routes",[22,27,31,35,39,43,47,51,55,59,63,67,71,75,79,84,88,92,96,100],{"id":23,"difficulty":24,"q":25,"a":26},"dynamic-segment-syntax","easy","How do you define a dynamic URL segment in React Router v6, and how do you read it inside the component?","A **dynamic segment** is a path token prefixed with `:`. React Router captures whatever the user types in that position and makes it available via **`useParams()`**, which returns an object keyed by segment name.\n\n```jsx\n\u002F\u002F Route definition\n\u003CRoute path=\"\u002Fusers\u002F:userId\" element={\u003CUserProfile \u002F>} \u002F>\n\n\u002F\u002F Inside UserProfile\nimport { useParams } from 'react-router-dom';\n\nfunction UserProfile() {\n  const { userId } = useParams(); \u002F\u002F e.g. \"42\"\n  \u002F\u002F userId is always a string — coerce if you need a number\n  return \u003Ch1>User {userId}\u003C\u002Fh1>;\n}\n```\n\n**Rule of thumb:** Name segments descriptively (`:userId`, not `:id`) so reading `useParams()` reads like documentation.\n",{"id":28,"difficulty":24,"q":29,"a":30},"params-always-strings","What is the type of a route param returned by useParams, and what bug does that cause?","**`useParams()` always returns strings**, even when the URL contains what looks like a number. This is a common source of subtle bugs when comparing params with strict equality (`===`) against numbers.\n\n```jsx\nfunction PostDetail() {\n  const { postId } = useParams(); \u002F\u002F \"5\" — a string\n\n  \u002F\u002F Bug: 5 === \"5\" is false in JavaScript\n  const post = posts.find(p => p.id === postId); \u002F\u002F undefined!\n\n  \u002F\u002F Fix: coerce before comparing\n  const post2 = posts.find(p => p.id === Number(postId));\n\n  return \u003Cdiv>{post2?.title}\u003C\u002Fdiv>;\n}\n```\n\n**Rule of thumb:** Always coerce params at the top of the component — `Number(id)`, `parseInt(id, 10)`, or validate with Zod — before passing them to any data layer.\n",{"id":32,"difficulty":24,"q":33,"a":34},"multiple-dynamic-segments","Can a single route path contain more than one dynamic segment? Give an example.","Yes. A path can contain **multiple dynamic segments**, each with a unique name. `useParams()` returns all of them in one object.\n\n```jsx\n\u002F\u002F Route definition — two segments\n\u003CRoute path=\"\u002Forgs\u002F:orgId\u002Frepos\u002F:repoId\" element={\u003CRepoDetail \u002F>} \u002F>\n\nfunction RepoDetail() {\n  const { orgId, repoId } = useParams();\n  \u002F\u002F e.g. orgId = \"acme\", repoId = \"dashboard\"\n\n  return (\n    \u003Cp>\n      Org: {orgId} \u002F Repo: {repoId}\n    \u003C\u002Fp>\n  );\n}\n```\n\n**Rule of thumb:** Destructure all params at once at the top of the component; if you need more than three segments, reconsider whether a flatter URL design would be clearer.\n",{"id":36,"difficulty":14,"q":37,"a":38},"splat-routes","What is a splat route in React Router v6 and when would you use one?","A **splat route** (also called a catch-all) uses `*` as the final segment. It matches everything after that point, and the captured string is available as `params[\"*\"]`. It is most often used for **custom 404 pages** or **legacy URL migration**.\n\n```jsx\n\u002F\u002F Catch-all at the root level — renders for any unmatched path\n\u003CRoute path=\"*\" element={\u003CNotFound \u002F>} \u002F>\n\n\u002F\u002F Catch-all inside a layout — only unmatched paths under \u002Fdocs\n\u003CRoute path=\"\u002Fdocs\">\n  \u003CRoute index element={\u003CDocsHome \u002F>} \u002F>\n  \u003CRoute path=\"*\" element={\u003CDocsFallback \u002F>} \u002F>\n\u003C\u002FRoute>\n\nfunction DocsFallback() {\n  const params = useParams();\n  \u002F\u002F params[\"*\"] = \"api\u002Fv2\u002Fmissing-page\"\n  return \u003Cp>Could not find docs page: {params[\"*\"]}\u003C\u002Fp>;\n}\n```\n\n**Rule of thumb:** Place the `*` route last among siblings — React Router matches in definition order and will never reach it if an earlier route already matches.\n",{"id":40,"difficulty":14,"q":41,"a":42},"optional-segments","How do you make a route segment optional in React Router v6?","Append `?` to any dynamic segment token to make it **optional**. The param will be `undefined` when the segment is absent.\n\n```jsx\n\u002F\u002F Both \u002Fsearch and \u002Fsearch\u002Fadvanced match this route\n\u003CRoute path=\"\u002Fsearch\u002F:mode?\" element={\u003CSearch \u002F>} \u002F>\n\nfunction Search() {\n  const { mode } = useParams();\n  \u002F\u002F mode is \"advanced\" | undefined\n\n  return (\n    \u003Cdiv>\n      {mode === 'advanced' ? \u003CAdvancedFilters \u002F> : \u003CBasicSearch \u002F>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**Rule of thumb:** Prefer two explicit routes (`\u002Fsearch` and `\u002Fsearch\u002F:mode`) over an optional segment when the two layouts differ significantly — it keeps each component focused.\n",{"id":44,"difficulty":24,"q":45,"a":46},"nested-routes-outlet","What is Outlet in React Router v6 and why is it needed for nested routes?","**`\u003COutlet \u002F>`** is a placeholder rendered by a parent route component that tells React Router where to inject the matched child route's element. Without it, child routes render nowhere — the URL changes but nothing appears on screen.\n\n```jsx\n\u002F\u002F Route config — Dashboard wraps two child routes\n\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      \u003Cnav>\n        \u003CNavLink to=\"\u002Fdashboard\">Overview\u003C\u002FNavLink>\n        \u003CNavLink to=\"\u002Fdashboard\u002Fsettings\">Settings\u003C\u002FNavLink>\n      \u003C\u002Fnav>\n      {\u002F* child route renders here *\u002F}\n      \u003Cmain>\u003COutlet \u002F>\u003C\u002Fmain>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**Rule of thumb:** Every parent route element that has `\u003CRoute>` children must render `\u003COutlet \u002F>`; forgetting it is the most common nested-route bug.\n",{"id":48,"difficulty":14,"q":49,"a":50},"layout-routes","What is a layout route and how does it differ from a regular route?","A **layout route** is a `\u003CRoute>` that has an `element` for shared UI (nav, sidebar, wrappers) but whose `path` is intentionally omitted or set to the parent prefix. It exists purely to provide structure — it never matches by itself; children match the actual URLs.\n\n```jsx\n\u002F\u002F createBrowserRouter style — no path on the layout route\nconst router = createBrowserRouter([\n  {\n    element: \u003CAppShell \u002F>,   \u002F\u002F nav + footer — no \"path\" key\n    children: [\n      { path: '\u002F',          element: \u003CHome \u002F> },\n      { path: '\u002Fabout',     element: \u003CAbout \u002F> },\n      { path: '\u002Fblog\u002F:slug', element: \u003CBlogPost \u002F> },\n    ],\n  },\n]);\n\n\u002F\u002F AppShell renders Outlet — no URL segment consumed\nfunction AppShell() {\n  return (\n    \u003C>\n      \u003CGlobalNav \u002F>\n      \u003COutlet \u002F>\n      \u003CFooter \u002F>\n    \u003C\u002F>\n  );\n}\n```\n\n**Rule of thumb:** Omit `path` on a layout route entirely; adding `path=\"\"` also works but is less readable.\n",{"id":52,"difficulty":24,"q":53,"a":54},"index-routes","What is an index route in React Router v6 and when does it render?","An **index route** is a child route with the `index` prop instead of a `path`. It renders inside the parent's `\u003COutlet \u002F>` when the URL matches the parent's path exactly — acting as the default child.\n\n```jsx\n\u003CRoute path=\"\u002Fteam\" element={\u003CTeamLayout \u002F>}>\n  {\u002F* renders at \u002Fteam exactly *\u002F}\n  \u003CRoute index element={\u003CTeamOverview \u002F>} \u002F>\n  {\u002F* renders at \u002Fteam\u002F:memberId *\u002F}\n  \u003CRoute path=\":memberId\" element={\u003CMemberDetail \u002F>} \u002F>\n\u003C\u002FRoute>\n\n\u002F\u002F Without the index route, navigating to \u002Fteam shows\n\u002F\u002F TeamLayout with an empty Outlet — a blank content area.\n```\n\n**Rule of thumb:** Every layout route that owns an `\u003COutlet \u002F>` should have an index route; otherwise the parent path renders a blank content area.\n",{"id":56,"difficulty":14,"q":57,"a":58},"relative-vs-absolute-paths","What is the difference between relative and absolute paths in nested route definitions?","In React Router v6, child `path` values are **relative** by default — they are appended to the parent's path. A **leading `\u002F` makes a path absolute**, bypassing the nesting entirely, which is usually a mistake inside a nested `\u003CRoute>` tree.\n\n```jsx\n\u003CRoute path=\"\u002Fapp\" element={\u003CAppLayout \u002F>}>\n  {\u002F* relative — matches \u002Fapp\u002Fprofile *\u002F}\n  \u003CRoute path=\"profile\" element={\u003CProfile \u002F>} \u002F>\n\n  {\u002F* absolute — matches \u002Fsettings, NOT \u002Fapp\u002Fsettings\n      The parent layout is still rendered because it is\n      an ancestor in the tree, but the URL ignores \u002Fapp *\u002F}\n  \u003CRoute path=\"\u002Fsettings\" element={\u003CSettings \u002F>} \u002F>\n\u003C\u002FRoute>\n\n\u002F\u002F Link usage follows the same rule:\n\u002F\u002F \u003CLink to=\"profile\">   — relative, resolves to \u002Fapp\u002Fprofile\n\u002F\u002F \u003CLink to=\"\u002Fprofile\">  — absolute, resolves to \u002Fprofile\n```\n\n**Rule of thumb:** Never start a nested child `path` with `\u002F`; reserve absolute paths for top-level routes only.\n",{"id":60,"difficulty":14,"q":61,"a":62},"deeply-nested-routes","How do you structure three levels of nesting — an app shell, a section layout, and a detail page?","Each level adds one more `\u003CRoute>` wrapper and one more `\u003COutlet \u002F>` in the corresponding component. React Router renders the entire ancestor chain, threading `\u003COutlet \u002F>` down the tree.\n\n```jsx\nconst router = createBrowserRouter([\n  {\n    \u002F\u002F Level 1 — app shell (nav + footer)\n    element: \u003CAppShell \u002F>,\n    children: [\n      {\n        \u002F\u002F Level 2 — section layout (sidebar)\n        path: 'docs',\n        element: \u003CDocsLayout \u002F>,\n        children: [\n          { index: true, element: \u003CDocsHome \u002F> },\n          {\n            \u002F\u002F Level 3 — detail page\n            path: ':slug',\n            element: \u003CDocPage \u002F>,\n          },\n        ],\n      },\n    ],\n  },\n]);\n\n\u002F\u002F AppShell renders \u003COutlet \u002F> → DocsLayout renders \u003COutlet \u002F> → DocPage\n```\n\n**Rule of thumb:** Keep nesting to three levels maximum; deeper trees are hard to reason about and usually signal that the URL design needs flattening.\n",{"id":64,"difficulty":14,"q":65,"a":66},"outlet-context","How do you pass data from a parent route component to its child routes without prop drilling?","Pass a value to **`\u003COutlet context={...} \u002F>`** in the parent and read it with **`useOutletContext()`** in any descendant. This avoids threading props through multiple intermediate components.\n\n```jsx\n\u002F\u002F Parent layout — fetches the user and passes it down\nfunction DashboardLayout() {\n  const user = useCurrentUser(); \u002F\u002F some hook or loader result\n\n  return (\n    \u003Cdiv>\n      \u003CDashboardNav user={user} \u002F>\n      {\u002F* provide user to all child routes *\u002F}\n      \u003COutlet context={{ user }} \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F Child route — reads the context\nimport { useOutletContext } from 'react-router-dom';\n\nfunction ProfilePage() {\n  const { user } = useOutletContext();\n  return \u003Ch1>Hello, {user.name}\u003C\u002Fh1>;\n}\n```\n\n**Rule of thumb:** Type the context with a custom hook — `export function useDashboardCtx() { return useOutletContext\u003CDashCtx>(); }` — so TypeScript catches mismatches.\n",{"id":68,"difficulty":14,"q":69,"a":70},"404-inside-nested-layout","How do you show a 404 page that still displays the parent layout's navigation?","Add a **splat child route** (`path=\"*\"`) inside the parent's children. It matches any unrecognised path under the parent and renders inside `\u003COutlet \u002F>`, so the layout's navigation remains visible.\n\n```jsx\n\u003CRoute path=\"\u002Fapp\" element={\u003CAppLayout \u002F>}>\n  \u003CRoute index element={\u003CHome \u002F>} \u002F>\n  \u003CRoute path=\"posts\" element={\u003CPosts \u002F>} \u002F>\n  \u003CRoute path=\"posts\u002F:id\" element={\u003CPostDetail \u002F>} \u002F>\n\n  {\u002F* catches \u002Fapp\u002Fanything-unrecognised *\u002F}\n  \u003CRoute path=\"*\" element={\u003CNotFound \u002F>} \u002F>\n\u003C\u002FRoute>\n\nfunction NotFound() {\n  return (\n    \u003Cdiv>\n      \u003Ch2>404 – Page not found\u003C\u002Fh2>\n      \u003CLink to=\"\u002Fapp\">Back home\u003C\u002FLink>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n**Rule of thumb:** Add a `path=\"*\"` child to every significant layout; relying on only a root-level catch-all means users lose their navigation context when they land on a 404.\n",{"id":72,"difficulty":14,"q":73,"a":74},"search-params-vs-route-params","What is the difference between route params and URL search params, and which hook reads each?","**Route params** (`:id`) are part of the URL path and identify a resource — they are read with `useParams()`. **Search params** (`?sort=asc&page=2`) are query string key-value pairs used for filtering, sorting, or pagination — they are read and written with `useSearchParams()`.\n\n```jsx\n\u002F\u002F URL: \u002Fproducts\u002F42?color=blue&size=M\n\nfunction ProductDetail() {\n  const { productId } = useParams();       \u002F\u002F \"42\"\n  const [searchParams, setSearchParams] = useSearchParams();\n\n  const color = searchParams.get('color'); \u002F\u002F \"blue\"\n  const size  = searchParams.get('size');  \u002F\u002F \"M\"\n\n  function sortByPrice() {\n    \u002F\u002F updates the query string without a full navigation\n    setSearchParams({ sort: 'price' });\n  }\n\n  return \u003Cp>{productId} — {color} \u002F {size}\u003C\u002Fp>;\n}\n```\n\n**Rule of thumb:** Use route params for identity (which resource), search params for state (how to display it) — mixing them leads to bloated URLs and broken back-button behaviour.\n",{"id":76,"difficulty":14,"q":77,"a":78},"create-browser-router-vs-jsx","What are the differences between createBrowserRouter (object config) and the JSX \u003CRoutes>\u002F\u003CRoute> approach?","**`createBrowserRouter`** is the modern Data API introduced in v6.4. It accepts a plain-object route tree and unlocks **loaders**, **actions**, **errorElement**, and **defer** at each route. The older **`\u003CRoutes>\u002F\u003CRoute>` JSX** approach still works but cannot use any Data API features.\n\n```jsx\n\u002F\u002F Object config — enables loaders and actions\nconst router = createBrowserRouter([\n  {\n    path: '\u002Fposts\u002F:id',\n    element: \u003CPostDetail \u002F>,\n    loader: ({ params }) => fetchPost(params.id), \u002F\u002F data API\n    errorElement: \u003CPostError \u002F>,\n  },\n]);\n\u002F\u002F Render: \u003CRouterProvider router={router} \u002F>\n\n\u002F\u002F JSX approach — no loader support\nfunction App() {\n  return (\n    \u003CBrowserRouter>\n      \u003CRoutes>\n        \u003CRoute path=\"\u002Fposts\u002F:id\" element={\u003CPostDetail \u002F>} \u002F>\n      \u003C\u002FRoutes>\n    \u003C\u002FBrowserRouter>\n  );\n}\n```\n\n**Rule of thumb:** Use `createBrowserRouter` for all new projects; it strictly supersedes the JSX approach and is what the React Router team recommends going forward.\n",{"id":80,"difficulty":81,"q":82,"a":83},"loader-pattern","hard","How does a route loader work in React Router v6.4+ and how does the component consume its data?","A **loader** is an async function attached to a route in the object config. React Router calls it before rendering the route, passing `{ params, request }`. The component reads the resolved data via **`useLoaderData()`** — no `useEffect` or loading state needed.\n\n```jsx\n\u002F\u002F Define the loader\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 returned value becomes loader data\n}\n\n\u002F\u002F Attach it to the route\nconst router = createBrowserRouter([\n  {\n    path: '\u002Fposts\u002F:postId',\n    element: \u003CPostDetail \u002F>,\n    loader: postLoader,\n    errorElement: \u003CPostError \u002F>,\n  },\n]);\n\n\u002F\u002F Consume in the component\nimport { useLoaderData } from 'react-router-dom';\n\nfunction PostDetail() {\n  const post = useLoaderData(); \u002F\u002F fully resolved — no loading state\n  return \u003Ch1>{post.title}\u003C\u002Fh1>;\n}\n```\n\n**Rule of thumb:** Throw a `Response` with an appropriate status code from a loader on errors — React Router will render `errorElement` and `useRouteError()` can read the response status.\n",{"id":85,"difficulty":81,"q":86,"a":87},"error-element-nested","How does errorElement bubble in a nested route tree?","When a loader or renderer throws, React Router walks **up the route tree** looking for the nearest ancestor that declares an **`errorElement`**. The first match renders in place of the failed subtree, preserving all ancestor layouts above it.\n\n```jsx\nconst router = createBrowserRouter([\n  {\n    element: \u003CAppShell \u002F>,        \u002F\u002F no errorElement — bubbles further up\n    children: [\n      {\n        path: 'docs',\n        element: \u003CDocsLayout \u002F>,\n        errorElement: \u003CDocsError \u002F>, \u002F\u002F catches errors in any docs child\n        children: [\n          {\n            path: ':slug',\n            element: \u003CDocPage \u002F>,\n            loader: docLoader,     \u002F\u002F throws 404 → DocsError renders\n          },\n        ],\n      },\n    ],\n  },\n]);\n\nfunction DocsError() {\n  const error = useRouteError(); \u002F\u002F the thrown Response or Error\n  return \u003Cp>Docs error: {error.statusText}\u003C\u002Fp>;\n}\n```\n\n**Rule of thumb:** Add an `errorElement` at every meaningful layout boundary so a child failure shows a scoped error UI instead of wiping the whole page.\n",{"id":89,"difficulty":24,"q":90,"a":91},"navigate-programmatic","How do you navigate programmatically to a dynamic route, including the param?","Use the **`useNavigate()`** hook. Call the returned function with the full path string (interpolate params yourself) or with a relative path and a `replace` flag when you want to skip history entries.\n\n```jsx\nimport { useNavigate } from 'react-router-dom';\n\nfunction UserList({ users }) {\n  const navigate = useNavigate();\n\n  function handleSelect(userId) {\n    \u002F\u002F absolute path with the param interpolated\n    navigate(`\u002Fusers\u002F${userId}`);\n\n    \u002F\u002F replace current history entry (e.g. after a form submission)\n    \u002F\u002F navigate(`\u002Fusers\u002F${userId}`, { replace: true });\n  }\n\n  return (\n    \u003Cul>\n      {users.map(u => (\n        \u003Cli key={u.id} onClick={() => handleSelect(u.id)}>\n          {u.name}\n        \u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n**Rule of thumb:** Prefer `\u003CLink>` over `useNavigate` for user-initiated navigation — it is more accessible and handles middle-click\u002Fopen-in-new-tab correctly.\n",{"id":93,"difficulty":24,"q":94,"a":95},"active-link-nested","How does NavLink determine its active state inside a nested route?","**`\u003CNavLink>`** compares the `to` prop against the current URL. By default it applies the `active` class when the URL **starts with** the `to` value (prefix match). Set `end` to `true` to require an exact match — essential for parent paths like `\u002Fdashboard`.\n\n```jsx\nfunction DocsNav() {\n  return (\n    \u003Cnav>\n      {\u002F* active for \u002Fdocs AND \u002Fdocs\u002Fanything — usually wrong for the root *\u002F}\n      \u003CNavLink to=\"\u002Fdocs\">Docs Home\u003C\u002FNavLink>\n\n      {\u002F* active only at \u002Fdocs exactly *\u002F}\n      \u003CNavLink to=\"\u002Fdocs\" end>Docs Home\u003C\u002FNavLink>\n\n      {\u002F* active for \u002Fdocs\u002Fapi and \u002Fdocs\u002Fapi\u002F... *\u002F}\n      \u003CNavLink to=\"\u002Fdocs\u002Fapi\">API Reference\u003C\u002FNavLink>\n    \u003C\u002Fnav>\n  );\n}\n```\n\n**Rule of thumb:** Always add `end` to `NavLink` items whose `to` value is an ancestor of other nav items; otherwise the parent link stays highlighted on every child page.\n",{"id":97,"difficulty":81,"q":98,"a":99},"route-param-validation","How would you validate a route param (e.g. ensure :id is numeric) and redirect if it is invalid?","The cleanest place is inside a **loader** — parse and validate the param, then throw a redirect `Response` if it fails. The component never renders with invalid data.\n\n```jsx\nimport { redirect } from 'react-router-dom';\n\nasync function postLoader({ params }) {\n  const id = Number(params.postId);\n\n  \u002F\u002F Redirect to 404 layout if id is not a valid integer\n  if (!Number.isInteger(id) || id \u003C= 0) {\n    throw redirect('\u002F404');           \u002F\u002F or throw new Response('', { status: 404 })\n  }\n\n  const res = await fetch(`\u002Fapi\u002Fposts\u002F${id}`);\n  if (!res.ok) throw new Response('Post not found', { status: 404 });\n  return res.json();\n}\n\nconst router = createBrowserRouter([\n  {\n    path: '\u002Fposts\u002F:postId',\n    loader: postLoader,\n    element: \u003CPostDetail \u002F>,\n    errorElement: \u003CPostError \u002F>,\n  },\n]);\n```\n\n**Rule of thumb:** Validate params in loaders, not in render; by the time the component runs the data should already be known-good.\n",{"id":101,"difficulty":81,"q":102,"a":103},"outlet-context-typescript","How do you type useOutletContext in TypeScript to avoid using any?","Create a **typed wrapper hook** that calls `useOutletContext` with the correct generic. Export it from the layout file so child routes import the typed version, not the raw hook.\n\n```tsx\n\u002F\u002F DashboardLayout.tsx\nimport { Outlet, useOutletContext } from 'react-router-dom';\n\ninterface DashboardCtx {\n  user: { id: number; name: string; role: string };\n  refetch: () => void;\n}\n\n\u002F\u002F Typed wrapper — export this, not useOutletContext directly\nexport function useDashboardCtx() {\n  return useOutletContext\u003CDashboardCtx>();\n}\n\nexport function DashboardLayout() {\n  const user = useCurrentUser();\n  const refetch = useRefetch();\n\n  return (\n    \u003Cdiv>\n      \u003CDashboardNav \u002F>\n      \u003COutlet context={{ user, refetch } satisfies DashboardCtx} \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F ProfilePage.tsx — fully typed, no any\nimport { useDashboardCtx } from '.\u002FDashboardLayout';\n\nfunction ProfilePage() {\n  const { user, refetch } = useDashboardCtx(); \u002F\u002F typed!\n  return \u003Ch1>{user.name}\u003C\u002Fh1>;\n}\n```\n\n**Rule of thumb:** Co-locate the typed hook with the layout component that provides the context — it acts as the single source of truth for the context shape.\n",20,null,{"description":11},"React Router v6 dynamic and nested routes interview questions — useParams, Outlet, layout routes, index routes, useOutletContext, splat routes, and data loaders.","react\u002Frouting\u002Fdynamic-nested-routes","Dynamic and Nested Routes","Routing","routing","2026-06-24","JPlp09gxgi2ITBy45JiCXT1aq106IlC4O1y4c0dPcbM",[115,119,120,124],{"subtopic":116,"path":117,"order":118},"Routing Basics","\u002Freact\u002Frouting\u002Frouting-basics",1,{"subtopic":109,"path":20,"order":12},{"subtopic":121,"path":122,"order":123},"Navigation Hooks","\u002Freact\u002Frouting\u002Fnavigation-hooks",3,{"subtopic":125,"path":126,"order":127},"Protected Routes","\u002Freact\u002Frouting\u002Fprotected-routes",4,{"path":129,"title":130},"\u002Fblog\u002Freact-dynamic-nested-routes-guide","React Router v6 Dynamic & Nested Routes — Complete Interview Guide",1782244101037]