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

  • Dashboard is the parent route.

  • Profile and Settings are 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

  • ProtectedRoute is a custom component that checks if the user is logged in.

  • If isAuthenticated is false, it redirects to /login using <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 ProtectedLayout wraps 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.