Why Are My React Router Nested Routes Not Working? Common Pitfalls and Fixes
React Router nested routes fail when the parent route lacks the exact prop (v5), omits {props.children} or <Outlet>, or when multiple router providers create conflicting contexts.
React Router nested routes are a powerful pattern for building modular UIs, but they require precise configuration to function correctly. According to the facebook/react repository's nesting fixtures, most failures stem from misunderstanding how the router builds its matching tree and renders child components. This guide explains why your nested routes may not be rendering and how to fix them using patterns validated by the React core team.
How React Router Builds the Route Tree
React Router constructs its routing tree by matching the current URL against the path props of <Route> components rendered within a router context (<BrowserRouter>, <HashRouter>, etc.). When a parent route matches, the router must still evaluate child routes to determine the final UI composition.
If a nested <Route> never receives the router context—or if the parent route's component never renders its children—the router cannot match deeper paths. This is why the fixtures/nesting demo in the React repository emphasizes maintaining a single, consistent router provider across the entire application tree.
Common Reasons React Router Nested Routes Fail
Missing exact Prop on Parent Routes (v5)
In React Router v5, the <Switch> component renders the first child <Route> that matches the current location. If the parent route uses path="/" without the exact prop, it matches every URL, preventing subsequent routes from ever being evaluated.
// ❌ Broken: Parent consumes all routes
<Switch>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
// ✅ Fixed: Add exact to parent or reorder
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
Parent Component Not Rendering Children
Even when routes match correctly, the UI won't appear if the parent component doesn't render its children. In React Router v5, you must explicitly render {props.children} or use the component/render prop to include nested <Switch> elements. In v6, you must use <Outlet>.
// ❌ Broken: Children never rendered (v5)
function Dashboard({ match }) {
return <h2>Dashboard</h2>;
// Missing {props.children} or nested Switch
}
// ✅ Fixed: Render children explicitly (v5)
function Dashboard({ match }) {
return (
<div>
<h2>Dashboard</h2>
<Switch>
<Route path={`${match.path}/stats`} component={Stats} />
<Route path={`${match.path}/settings`} component={Settings} />
</Switch>
</div>
);
}
Multiple Router Providers Breaking Context
Creating a new router instance inside a child component—such as wrapping a lazily-loaded subtree with its own <BrowserRouter>—breaks the shared context. Each router maintains its own history object, causing the parent and child routes to operate in isolated namespaces.
The fixtures/nesting/src/modern/lazyLegacyRoot.js file in the React repository demonstrates how to lazy load components while preserving the single router context established at the application root.
Incorrect Route Ordering
When using <Switch> (v5) or <Routes> (v6), order matters. Generic paths like path="/" or path="/dashboard" should appear after more specific routes, or they will intercept matches intended for deeper paths like /dashboard/stats.
Working Examples from the React Repository
The React repository's fixtures/nesting directory provides validated implementations for both React Router v5 and v6 patterns.
React Router v5: Nested Routes with match.path
This pattern from fixtures/nesting/src/modern/App.js uses match.path to build relative routes and explicitly renders a nested <Switch>:
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
} from 'react-router-dom';
function Dashboard({ match }) {
return (
<div>
<h2>Dashboard</h2>
<ul>
<li><Link to={`${match.url}/stats`}>Stats</Link></li>
<li><Link to={`${match.url}/settings`}>Settings</Link></li>
</ul>
<Switch>
<Route path={`${match.path}/stats`} component={Stats} />
<Route path={`${match.path}/settings`} component={Settings} />
<Route render={() => <p>Select an option.</p>} />
</Switch>
</div>
);
}
function Stats() { return <p>Stats page</p>; }
function Settings() { return <p>Settings page</p>; }
function Home() { return <h2>Home</h2>; }
function About() { return <h2>About</h2>; }
export default function App() {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/dashboard" component={Dashboard} />
<Route render={() => <h3>Page not found</h3>} />
</Switch>
</Router>
);
}
React Router v6: Using <Outlet> for Nested UI
For applications using React Router v6, the fixtures/nesting concepts translate to using <Outlet> instead of match objects:
import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";
function Layout() {
return (
<div>
<nav>
<Link to="/">Home</Link> | <Link to="dashboard">Dashboard</Link>
</nav>
<hr />
<Outlet />
</div>
);
}
function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
<ul>
<li><Link to="stats">Stats</Link></li>
<li><Link to="settings">Settings</Link></li>
</ul>
<Outlet />
</div>
);
}
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<h2>Home</h2>} />
<Route path="about" element={<h2>About</h2>} />
<Route path="dashboard" element={<Dashboard />}>
<Route path="stats" element={<p>Stats page</p>} />
<Route path="settings" element={<p>Settings page</p>} />
</Route>
<Route path="*" element={<h3>Page not found</h3>} />
</Route>
</Routes>
</BrowserRouter>
);
}
Key Files in the React Nesting Fixture
The facebook/react repository includes a dedicated fixtures/nesting directory that demonstrates these patterns across React versions. These files illustrate how to maintain a single router context while supporting both modern and legacy React roots.
Summary
- Use a single router provider: Wrap your application with one
<BrowserRouter>or<HashRouter>at the root. Nesting additional routers breaks the shared context required for route matching. - Render children explicitly: In React Router v5, parent components must render
{props.children}or a nested<Switch>. In v6, use the<Outlet>component to display nested route elements. - Control matching behavior: Add the
exactprop to parent routes in v5 to prevent them from consuming all URLs, or order routes from most-specific to least-specific within<Switch>or<Routes>. - Use relative paths correctly: Build nested paths using
match.pathandmatch.urlin v5, or declare relative paths in v6 nested route definitions.
Frequently Asked Questions
Why does my parent route hide all my child routes?
Your parent route likely lacks the exact prop (in v5) or is defined with a path that matches every URL (like path="/"). When using <Switch>, the router stops at the first match. Without exact, the parent consumes the URL, preventing deeper routes from evaluating. Place specific routes before generic ones or add exact to the parent path.
What is the difference between match.path and match.url in nested routes?
match.path is the route path pattern used for matching (e.g., /dashboard/:id), while match.url is the actual matched portion of the URL (e.g., /dashboard/123). Use match.path when defining nested <Route path={${match.path}/subroute} /> patterns, and use match.url when creating <Link to={${match.url}/subroute} /> navigation targets.
Why do I need to use <Outlet> in React Router v6?
React Router v6 replaces the implicit props.children rendering pattern with an explicit <Outlet> component. When a parent route matches, the router looks for an <Outlet> in that component's render output to determine where to insert the child route element. Omitting <Outlet> results in a blank page even when the URL matches a nested path correctly.
Can I use nested routes with lazy-loaded components?
Yes, but you must ensure the lazy-loaded component renders the router context correctly. As shown in fixtures/nesting/src/modern/lazyLegacyRoot.js, you should define your routes and <Switch> or <Routes> inside the lazy component, but keep the <BrowserRouter> at the application root. Never wrap a lazy-loaded subtree with its own <BrowserRouter>, as this creates a disconnected routing context.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →