Nested Routes & Route Guards in React Router Explained with Examples
When building modern React applications, you often have complex layouts like dashboards, profiles, or admin panels — where multiple pages share common UI parts like a sidebar, header, or footer.
You also need security controls — for example, only logged-in users should access certain routes.
React Router provides two powerful tools for this:
Nested Routes – for structured, hierarchical layouts.
Route Guards – for protecting private or restricted pages.
Nested Routes
What Are Nested Routes?
Nested routes allow you to organize routes in a parent-child hierarchy.
This helps when multiple pages share a common layout or structure.
Example:
/dashboard
/dashboard/profile
/dashboard/settings Here, /dashboard is the parent route, and the other two are child routes.
Step-by-Step Example
App Structure
import React from "react";
import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";
function Dashboard() {
return (
<div>
<h2>Dashboard Layout</h2>
<nav>
<Link to="profile">Profile</Link> |{" "}
<Link to="settings">Settings</Link>
</nav>
{/* Child Routes will render here */}
<Outlet />
</div>
);
}
function Profile() {
return <h3> User Profile Page</h3>;
}
function Settings() {
return <h3>⚙️ Account Settings Page</h3>;
}
function App() {
return (
<BrowserRouter>
<Routes>
{/* Parent Route */}
<Route path="/dashboard" element={<Dashboard />}>
{/* Nested Child Routes */}
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
Explanation
Dashboardis the parent route.ProfileandSettingsare child routes inside it.The
<Outlet />component acts as a placeholder where child components render.
So when you visit /dashboard/profile, React Router first loads Dashboard, then inserts the Profile component into <Outlet />.
Why Use Nested Routes?
- Reuse shared layouts (like sidebars, navbars).
- Cleaner URL hierarchy.
- Easier to maintain modular structure.
- Improves navigation flow and app organization
Nested Routes with Default Child
You can set a default child route that renders when no sub-path is specified.
<Route path="/dashboard" element={<Dashboard />}> <Route index element={<Profile />} /> {/* Default route */} <Route path="settings" element={<Settings />} />
</Route> When user visits /dashboard, the Profile page loads by default.
Route Guards (Protected Routes)
What Are Route Guards?
Route Guards prevent unauthorized users from accessing certain pages.
For example:
Only logged-in users can access
/dashboard.Guests should be redirected to
/login.
Basic Protected Route Example
import React from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
function Dashboard() {
return <h2>Welcome to Dashboard 🚀</h2>;
}
function Login() {
return <h2>Please Log In 🔐</h2>;
}
function ProtectedRoute({ isAuthenticated, children }) {
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
}
function App() {
const isAuthenticated = false; // simulate login state
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute isAuthenticated={isAuthenticated}>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
Explanation
ProtectedRouteis a custom component that checks if the user is logged in.If
isAuthenticatedis false, it redirects to/loginusing<Navigate />.Otherwise, it renders the child component (
Dashboard).
Combining Nested Routes with Route Guards
You can protect entire route groups like dashboards with multiple subpages.
import { BrowserRouter, Routes, Route, Navigate, Outlet } from "react-router-dom";
function ProtectedLayout({ isAuthenticated }) {
return isAuthenticated ? <Outlet /> : <Navigate to="/login" />;
}
function Dashboard() {
return (
<div>
<h2> Dashboard</h2>
<Outlet />
</div>
);
}
function Profile() {
return <h3> Profile Page</h3>;
}
function Settings() {
return <h3> Settings Page</h3>;
}
function Login() {
return <h2> Login Page</h2>;
}
function App() {
const isAuthenticated = true; // simulate logged-in user
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
{/* Protected Routes */}
<Route element={<ProtectedLayout isAuthenticated={isAuthenticated} />}>
<Route path="/dashboard" element={<Dashboard />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
Explanation:
The
ProtectedLayoutwraps around all dashboard routes.If not logged in, the user is redirected to
/login.If logged in, child routes render inside
<Outlet />.
Tips & Best Practices
- Use Layout Routes for shared UI like headers or sidebars.
- Keep Protected Routes modular (don’t repeat guard logic everywhere).
- Use Context or Redux to store authentication state globally.
- Handle loading states if authentication check depends on an API.
Common Mistakes
- Forgetting
<Outlet />→ child routes won’t render. - Not using
<Navigate />for redirects → unauthorized users see blank pages. - Using nested paths without relative URLs → links may break.
- Forgetting to define a default (index) route → parent renders empty.