/admin-tree → A tree-defined admin section using createHierarchy(). Inherits breadcrumbs and
permissions down the chain. (Requires admin — use the example header's Toggle 👤 button to switch users.)What route inheritance gives you
When the routes in your app form a natural hierarchy
(/documents → /documents/:id → /documents/:id/logs),
the router can compute inherited metadata for each child instead of asking you to repeat it:
- Breadcrumbs — concatenated parent → child
- Permissions — child permissions are added to the parent's (cumulative AND, like filesystem ACLs)
- Conditions — parent
wrap({ conditions })run before child conditions - Authorization callbacks — parent callbacks execute before child callbacks (fail-fast on first
false)
Inheritance is opt-in. Without it enabled, routes stay independent and you define everything explicitly. Enable it once at app startup:
Which API should I use?
Two ways to declare the hierarchy. Both produce identical runtime behavior — they're just different ergonomics for writing route definitions.
| Use… | When | Looks like |
|---|---|---|
Flat definitionscreateRoute() + setHierarchicalRoutesEnabled(true) | Mostly shallow routes (1–2 levels), or you need inheritX: false opt-outs on specific routes, or your route shape doesn't visually mirror your UI tree. | One flat object with '/a', '/a/b', '/a/b/c' keys — child paths spelled out in full. |
Tree definitionscreateHierarchy() | Deep nesting (3+ levels) where every child inherits from its parent and the structure mirrors UI hierarchy. Most concise for admin-style apps. | Single nested object with children: { ':id': { … } }. Child paths are relative — no repeated segments. |
{ ...adminRoutes, ...publicRoutes }.API 1 — Flat definitions with createRoute()
Standard flat-object route declarations. Inheritance triggers automatically when setHierarchicalRoutesEnabled(true) is set and the router can find a parent
path by prefix match.
Breadcrumbs
Permissions (cumulative AND)
Permission checks chain like filesystem permissions — to reach a nested route, you must pass every ancestor check too.
Authorization callbacks chain too
Opting out of inheritance
The flat API supports per-route opt-out flags. The tree API doesn't — it always inherits. This is the main reason to choose flat over tree.
API 2 — Tree definitions with createHierarchy()
createHierarchy() takes a nested object and returns a flat routes object that <Router> can consume. Child paths are relative —
the helper concatenates them to the parent path for you. Inheritance is always on inside the tree.
Side-by-side comparison
Path concatenation rules
- Child paths join with
/ - Leading slashes in child paths are stripped (
'logs'and'/logs'both produce the same result) - Parameters (
:id) work in any position - Catch-all
*can be used as a child key
Optional route names
Add name only on routes you'll navigate to with push(name, params).
Unnamed children still get a path — they're just not in the named-routes registry.
Realistic example: admin app with permissions + authorization
Combining tree + flat
Both forms return plain route objects — spread them into the routes prop:
Implementation notes
Parent discovery
/documents/:id/logs→ parent is/documents/:id/documents/:id→ parent is/documents/documents→ no parent (root)
The longest matching prefix wins. Computed once during route registration, not on every navigation.
Circular references
Built-in detection prevents infinite loops if routes somehow form a cycle. Mostly a non-issue with normal route definitions, but worth knowing it's caught.
See also
- Permissions — role-based access control + reactive
hasPermission() - Navigation guards — exit guards (separate concept from
wrap({ conditions })entry guards) - Route metadata — breadcrumbs, titles,
routeContext() - Named routes — programmatic navigation with refactor-safe names