What Are Nested Routes?
Nested routes are exactly what they sound like โ routes inside of other routes. They let you define a hierarchy where child routes render inside parent route layouts.
Think of it like a house. You have rooms (child routes) inside a building (parent route). The building provides the roof, walls, and foundation. Each room just handles its own furniture and decoration.
Most real apps need nested routes. A dashboard has a sidebar that stays constant while the main content area changes. A shop has a product list page with individual product details inside it.
Organizing Routes Hierarchically
Instead of listing every route on the same level, you nest child routes inside parent routes. This makes your route structure mirror your UI structure.
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
</Route>
</Routes>
</BrowserRouter>
);
}
See how Home, About, and Contact are children of the Layout route? That means Layout renders on every navigation, and only the child content swaps out. No re-mounting shared UI elements.
The index route is special โ it matches when the parent path matches exactly. So / renders Home, while /about renders About.
Try it Yourself โThe Outlet Component
Here's how nested routes actually render inside their parent. You use the Outlet component as a placeholder in the parent layout.
function Layout() {
return (
<div className="app">
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
<main>
<Outlet />
</main>
</div>
);
}
The Outlet component is where your child routes get rendered. It's like a window in the layout โ whatever child route matches the current URL shows up right there.
Your layout can include any shared UI โ headers, sidebars, footers, navigation. The Outlet just fills in the content that changes. This pattern eliminates duplicate code and keeps your layouts consistent.
Try it Yourself โLayout Routes
Layout routes are parent routes that don't have their own path. They exist purely to wrap child routes with shared UI. They're perfect for auth-protected areas or dashboard sections.
<Routes>
<Route element={<PublicLayout />}>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
</Route>
<Route element={<ProtectedLayout />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Route>
</Routes>
Notice how ProtectedLayout has no path prop. It's not a page you navigate to โ it's a wrapper. It can check if the user is authenticated, show a sidebar, or do whatever shared setup the child routes need.
This pattern is incredibly clean. Your protected pages don't need to worry about authentication checks โ the layout route handles it before any child renders. If the user isn't logged in, you redirect them from the layout, and none of the child components even mount.