react-router-rutas-anidadas

Nested Routes and Layouts in React Router

  • 4 min

Until now, our routes worked like a “switch”: either Component A is displayed, or Component B is displayed.

But modern web interfaces are not that simple. We often have a boxes within boxes structure.

Think of an Admin Dashboard.

  • You have a sidebar on the left that is always there.
  • You have a header at the top that is always there.
  • Only the central content changes when you navigate between “Profile”, “Settings”, or “Statistics”.

If you did this the way we’ve learned so far, you would have to repeat <Sidebar /> and <Header /> inside each page. That duplicates code and, worse still, unmounts and remounts those components with every navigation, losing their state (like text in a search bar).

The solution is Nested Routes and the component that leaves the slot: Outlet.

The Concept: Layouts and “Holes”

Think of a Layout as a master template. It’s a component that draws the common structure (Navbar, Footer, Sidebar) and leaves a reserved “hole” where the specific content of the child route will be rendered.

That “hole” in React Router is called <Outlet />.

Let’s create a component that will serve as a wrapper.

// src/layouts/MainLayout.jsx
import { Outlet, Link } from 'react-router';

export default function MainLayout() {
  return (
    <div className="main-layout">
      {/* 1. Fixed elements (Persistent) */}
      <nav>
        <Link to="/">Home</Link> | <Link to="/blog">Blog</Link>
      </nav>
      
      <hr />

      {/* 2. The hole where child routes will be rendered */}
      <main>
        <Outlet />
      </main>

      {/* 3. Fixed Footer */}
      <footer>© 2024 My Website</footer>
    </div>
  );
}
Copied!

If you don’t put the <Outlet /> component, the child routes will never be rendered. This is the most common mistake when starting out.

Now let’s go to App.jsx. Instead of placing routes one after another, we will put some inside others.

// src/App.jsx
import { Routes, Route } from 'react-router';
import MainLayout from './layouts/MainLayout';
import Home from './pages/Home';
import Blog from './pages/Blog';

function App() {
  return (
    <Routes>
      {/* Parent Route: The Layout */}
      <Route path="/" element={<MainLayout />}>
        
        {/* Child Routes (Nested Routes) */}
        
        {/* Index Route: Displayed when the URL is exactly "/" */}
        <Route index element={<Home />} />
        
        {/* Relative path: Added to the parent's path ("/blog") */}
        <Route path="blog" element={<Blog />} />
        
      </Route>
    </Routes>
  );
}
Copied!

What happens here?

  1. When the user enters /, React Router sees it matches the parent (MainLayout).
  2. It renders <MainLayout />.
  3. Inside the Layout, it finds the <Outlet />.
  4. Since the URL is /, it looks for the child route marked as index (Home).
  5. It injects <Home /> inside the <Outlet />.

If the user navigates to /blog:

  1. <MainLayout /> remains intact (it is not unmounted).
  2. React Router changes what is inside the <Outlet />: it removes <Home /> and puts <Blog />.

Multiple Layouts

The beauty of this is that we can have multiple layouts in the same application.

For example, your website might have a public part (with a normal Navbar) and a private admin area (with a Sidebar and dark colors).

<Routes>
  {/* PUBLIC ZONE */}
  <Route path="/" element={<PublicLayout />}>
    <Route index element={<Home />} />
    <Route path="login" element={<Login />} />
  </Route>

  {/* PRIVATE ZONE (Dashboard) */}
  {/* All routes will start with /app/ ... */}
  <Route path="/app" element={<DashboardLayout />}>
    <Route index element={<Summary />} />           {/* /app */}
    <Route path="profile" element={<Profile />} />    {/* /app/profile */}
    <Route path="settings" element={<Settings />} />  {/* /app/settings */}
  </Route>
</Routes>
Copied!

Here, DashboardLayout will have its own structure (perhaps a sidebar) and its own <Outlet>.