react-router-rutas-anidadas

Rutas Anidadas y Layouts en React Router

  • 4 min

Hasta ahora, nuestras rutas funcionaban como un “interruptor”: o se muestra el Componente A, o se muestra el Componente B.

Pero las interfaces web modernas no son tan simples. A menudo tenemos una estructura de cajas dentro de cajas.

Pensad en un Panel de Administración (Dashboard).

  • Tenéis una barra lateral (Sidebar) a la izquierda que siempre está ahí.
  • Tenéis una barra superior (Header) que siempre está ahí.
  • Y solo cambia el contenido central cuando navegáis entre “Perfil”, “Ajustes” o “Estadísticas”.

Si hicierais esto como hemos aprendido hasta ahora, tendríais que repetir el <Sidebar /> y el <Header /> dentro de cada página. Eso duplica código y, peor aún, desmonta y monta esos componentes en cada navegación, perdiendo su estado (como un texto en la barra de búsqueda).

La solución son las Rutas Anidadas (Nested Routes) y el componente que deja el hueco: Outlet.

El concepto: Layouts y “Agujeros”

Pensad en un Layout como una plantilla maestra. Es un componente que pinta la estructura común (Navbar, Footer, Sidebar) y deja un “hueco” reservado donde se pintará el contenido específico de la ruta hija.

Ese “hueco” en React Router se llama <Outlet />.

Vamos a crear un componente que servirá de envoltorio.

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

export default function MainLayout() {
  return (
    <div className="layout-principal">
      {/* 1. Elementos fijos (Persistentes) */}
      <nav>
        <Link to="/">Home</Link> | <Link to="/blog">Blog</Link>
      </nav>
      
      <hr />

      {/* 2. El hueco donde se renderizarán las rutas hijas */}
      <main>
        <Outlet />
      </main>

      {/* 3. Footer fijo */}
      <footer>© 2024 Mi Web</footer>
    </div>
  );
}
Copied!

Si no ponéis el componente <Outlet />, las rutas hijas no se renderizarán nunca. Es el error más común al empezar.

Ahora vamos a App.jsx. En lugar de poner las rutas una detrás de otra, vamos a meter unas dentro de otras.

// 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>
      {/* Ruta Padre: El Layout */}
      <Route path="/" element={<MainLayout />}>
        
        {/* Rutas Hijas (Nested Routes) */}
        
        {/* Index Route: Se muestra cuando la URL es exactamente "/" */}
        <Route index element={<Home />} />
        
        {/* Path relativo: Se suma al del padre ("/blog") */}
        <Route path="blog" element={<Blog />} />
        
      </Route>
    </Routes>
  );
}
Copied!

¿Qué ocurre aquí?

  1. Cuando el usuario entra en /, React Router ve que coincide con el padre (MainLayout).
  2. Renderiza <MainLayout />.
  3. Dentro del Layout, encuentra el <Outlet />.
  4. Como la URL es /, busca la ruta hija marcada como index (Home).
  5. Inyecta <Home /> dentro del <Outlet />.

Si el usuario navega a /blog:

  1. <MainLayout /> se mantiene intacto (no se desmonta).
  2. React Router cambia lo que hay en el <Outlet />: quita <Home /> y pone <Blog />.

Múltiples layouts

La gracia de esto es que podemos tener varios layouts en la misma aplicación.

Por ejemplo, vuestra web puede tener una parte pública (con Navbar normal) y una zona privada de administración (con Sidebar y colores oscuros).

<Routes>
  {/* ZONA PÚBLICA */}
  <Route path="/" element={<PublicLayout />}>
    <Route index element={<Home />} />
    <Route path="login" element={<Login />} />
  </Route>

  {/* ZONA PRIVADA (Dashboard) */}
  {/* Todas las rutas empezarán por /app/ ... */}
  <Route path="/app" element={<DashboardLayout />}>
    <Route index element={<Resumen />} />           {/* /app */}
    <Route path="perfil" element={<Perfil />} />    {/* /app/perfil */}
    <Route path="ajustes" element={<Ajustes />} />  {/* /app/ajustes */}
  </Route>
</Routes>
Copied!

Aquí, DashboardLayout tendrá su propia estructura (quizás un Sidebar lateral) y su propio <Outlet>.