Skip to content
This page has been auto-translated and may contain errors.View in English

Rutas anidadas y layouts

Routing cubrió la versión plana de la historia: una Route por página, cada una con su propio path y element. Las aplicaciones reales rápidamente superan eso, porque las URLs se anidan. Una ruta como /host/income tiene dos partes, y a menudo la página en la ruta más profunda mantiene la mayor parte de la página en la ruta más superficial: la misma navbar, las mismas pestañas de sección, con contenido nuevo intercalado abajo. Las rutas anidadas son cómo React Router expresa eso. Anidas elementos Route uno dentro de otro, y el router renderiza toda la cadena coincidente de una vez, los padres envolviendo a los hijos.

Entonces "rutas anidadas" significa dos cosas relacionadas. Los segmentos de URL se anidan, y la interfaz se anida con ellos: partes de la página persisten mientras navegas más profundamente, y solo la parte interna cambia. El segundo es el beneficio real.

Anidar rutas y renderizar hijos con Outlet

Route funciona como un elemento de cierre automático, y también funciona con hijos. Sus hijos deben ser otros elementos Route. Cuando una ruta hija coincide, React Router renderiza primero el elemento del padre, y el padre decide dónde aparece el elemento del hijo renderizando un Outlet.

jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<Home />} />
          <Route path="about" element={<About />} />
          <Route path="vans" element={<Vans />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}
jsx
import { Outlet } from 'react-router-dom'
import Header from './Header'

export default function Layout() {
  return (
    <>
      <Header />
      <Outlet />
    </>
  )
}

Outlet abre un agujero en el elemento del padre donde el hijo coincidente se renderiza. Juega el mismo papel que la prop children en composición: el padre renderiza la estructura compartida y marca el lugar donde va la parte variable. La diferencia es que React Router llena el agujero, eligiendo cuál ruta hija coincide con la URL actual. Sin un Outlet, el elemento del padre toma el control de la página y el hijo coincidente no se renderiza en ningún lado, que es lo primero que debes revisar cuando una página anidada sale en blanco.

Esa ruta externa de arriba es una ruta de layout: tiene un element y ningún path. Una ruta sin path coincide con cada URL que sus hijos coinciden, así que Layout envuelve cada página, que es exactamente dónde pertenece el chrome de sitio como un encabezado y pie de página. Un encabezado renderizado junto a Routes hace el mismo trabajo mientras la app tiene exactamente un layout; las rutas de layout son lo que permite a una sección como /host intercambiar chrome diferente sin tocar las páginas debajo. La opción a evitar es repetir el encabezado dentro de cada componente de página.

Layouts con un path

Una ruta de layout también puede llevar un path, que limita la interfaz compartida a una sección de la app. El proyecto VanLife del curso agrega una sección /host con su propia nav secundaria, así que las páginas del host obtienen un segundo layout anidado dentro del primero:

jsx
<Route element={<Layout />}>
  <Route path="/" element={<Home />} />
  <Route path="about" element={<About />} />
  <Route path="host" element={<HostLayout />}>
    <Route path="income" element={<Income />} />
    <Route path="reviews" element={<Reviews />} />
  </Route>
</Route>

HostLayout renderiza la nav del host más un Outlet, así que en /host/income el router renderiza Layout, luego HostLayout en su outlet, luego Income en el outlet del host layout. Tres rutas coinciden a la vez, y cada padre enmarca el siguiente. Los layouts se apilan tan profundamente como el diseño lo requiera.

Rutas index

Hay una brecha en esa configuración: HostLayout se renderiza en /host, pero su outlet no tiene nada que mostrar allí, porque cada hijo suma un segmento a la ruta. La solución es una ruta index: reemplaza path con la prop index, y ese hijo se renderiza en el outlet del padre en la ruta del padre mismo.

jsx
<Route index element={<Dashboard />} />

Ahora /host muestra la nav del host con el dashboard adentro, y /host/income intercambia el dashboard por la página de ingresos. Piensa en index como "el hijo por defecto": qué pertenece en el outlet antes de que se agregue algún segmento más profundo. También funciona en el nivel superior, donde <Route index element={<Home />} /> es la página de inicio dentro del layout raíz, la misma ruta que el ejemplo de apertura escribió como path="/".

Rutas relativas y enlaces relativos

Observa los paths de los hijos arriba: income, sin barra diagonal inicial. Un path que comienza con / es absoluto, medido desde la raíz del sitio. Un path sin uno es relativo a su ruta padre, así que el income anidado significa /host/income. Esto mantiene los árboles de rutas profundas mantenibles: renombra host a admin en un lugar y cada ruta descendiente lo sigue.

Los enlaces reciben el mismo tratamiento. Un Link renderizado dentro de un elemento enrutado resuelve paths relativos contra la ruta que lo renderizó, así que dentro de HostLayout la nav puede soltar el prefijo /host completamente:

jsx
import { Link, Outlet } from 'react-router-dom'

export default function HostLayout() {
  return (
    <>
      <nav>
        <Link to=".">Dashboard</Link>
        <Link to="income">Income</Link>
        <Link to="reviews">Reviews</Link>
      </nav>
      <Outlet />
    </>
  )
}

Dos valores especiales toman su significado de los paths del sistema de archivos. to="." enlaza a la ruta actual, que es cómo el enlace Dashboard apunta a /host en sí mismo sin codificarlo. to=".." sube hacia arriba, útil para enlaces "volver a la lista"; viene con una sutileza, ya que sube una ruta en lugar de un segmento de URL, que comienza a importar una vez que una sola ruta posee varios segmentos, y Route params and location lo pone en funcionamiento en un botón atrás real.

NavLink y coincidencia de ancestros

Intercambia esos elementos Link por NavLink para estilizar la página actual, como el curso hace para la nav del host, y entra en juego la coincidencia de ancestros: el router cuenta cada ancestro de la URL actual como coincidente, así que el enlace Dashboard to="." reporta activo en /host/income y /host/reviews también. Agrega end a ese único enlace y se mantiene sin estilo en cualquier lugar debajo de /host.

Pasar datos hacia abajo con useOutletContext

Un layout a menudo posee datos que sus hijos necesitan. HostVanDetail en el curso obtiene una van y la mantiene en estado, luego renderiza una nav de pestañas y un Outlet donde aparece la página de info, pricing o fotos. Los props no pueden llegar a través de un Outlet, ya que el layout nunca renderiza esos componentes directamente. La respuesta de React Router es la prop context en Outlet, leída del otro lado con useOutletContext:

jsx
<Outlet context={{ currentVan }} />
jsx
import { useOutletContext } from 'react-router-dom'

export default function HostVanInfo() {
  const { currentVan } = useOutletContext()

  return <h4>Name: {currentVan.name}</h4>
}

Sea cual sea el valor que pases a context es lo que useOutletContext devuelve en el hijo que actualmente llena el outlet. Pasar un objeto y desestructurar en el extremo receptor es la forma común, y deja espacio para agregar más valores después. Si Context te es familiar, esto se sentirá familiar también: el outlet actúa como un proveedor limitado a exactamente un agujero en la página.

Anidar o no anidar

Anidar vale la pena cuando las rutas comparten interfaz. Esa es la prueba. La URL sola puede tentarte a anidar: /vans y /vans/:id comparten un segmento, ¿por qué no compartir una ruta? Pero en VanLife esas dos páginas no comparten nada visible más allá del chrome de sitio, y anidarlas fuerza una forma incómoda: una ruta padre con un path y ningún element, que retrocede a renderizar un Outlet desnudo, más un hijo index para renderizar la lista. Esa es más estructura para cero beneficio.

Cuando hay interfaz compartida para mantener en pantalla, anida y dale al padre un elemento de layout con un Outlet. Cuando la única motivación es evitar un segmento de path repetido, dos rutas planas son la opción más simple y clara.

Nota de versión

Los ejemplos aquí importan de react-router-dom, que funciona en v6 y v7; Routing cubre qué cambió v7 sobre los paquetes. Los conceptos de rutas anidadas son idénticos en ambas versiones.

La coincidencia es una operación de árbol. React Router clasifica cada rama del árbol de rutas contra la URL y renderiza toda la rama ganadora, elemento más externo primero, cada Outlet recibiendo el siguiente elemento hacia abajo. Una ruta de layout sin path no contribuye nada a la coincidencia; solo inyecta un elemento en la cadena, que es por qué puede envolver todo. Esto también explica el modo de falla donde un elemento padre sin un Outlet silenciosamente se traga sus hijos: la coincidencia tuvo éxito, la rama tiene tres rutas de largo, pero la cadena de renderizado no tiene agujero para continuar.

La resolución relativa sigue el árbol de rutas en lugar de la cadena de URL, y esa distinción muerde con segmentos dinámicos. Desde un componente renderizado por <Route path="vans/:id"> anidado bajo host, to=".." se resuelve a /host, saltando tanto :id como vans, porque .. sube una ruta y esa sola ruta posee ambos segmentos. Cuando quieres el comportamiento de segmento de URL, acepta con <Link to=".." relative="path">. Ninguno por defecto es incorrecto; el enlace relativo a ruta sobrevive renombramientos de path, mientras que el enlace relativo a path coincide con la intuición del usuario para botones atrás.

useOutletContext es literalmente context bajo el capó: Outlet envuelve el hijo coincidente en un proveedor. Eso le da el comportamiento de re-renderizado de context, así que un nuevo objeto literal pasado a la prop context significa que cada componente leyendo useOutletContext se re-renderiza siempre que lo haga el layout, que rara vez es un problema a escala de página ya que navegar intercambia el subárbol de todos modos.

Elige entre los tres mecanismos de entrega por alcance: props donde renderizas el hijo tú mismo, outlet context para un layout alimentando la página que llena su agujero, y context completo cuando componentes fuera del subárbol enrutado necesitan el valor también. Una precaución estructural: outlet context acopla hijos a la forma del layout padre. Un componente que lee useOutletContext solo funciona montado bajo un layout que proporciona esa forma, así que mantén el valor de context pequeño y trátalo como parte de la API pública del layout.

JunoAnida rutas para mantener la interfaz compartida en la pantalla Cuando las páginas comparten partes de la pantalla, como una navbar que debe permanecer en su lugar mientras el contenido debajo cambia, puedes anidar una ruta dentro de otra. La ruta padre renderiza la parte compartida y coloca un Outlet donde debe aparecer la página hija.

Una ruta con un element y sin path es un layout que envuelve todo, y una ruta index es el hijo por defecto mostrado en la dirección del padre mismo.

Si dos páginas no comparten nada en pantalla, no hay necesidad de anidarlas.

JunoAnida rutas para mantener la interfaz compartida en la pantalla Envuelve rutas hijas en una Route padre, dale al padre un elemento de layout con un Outlet, y marca el hijo por defecto con index. Suelta las barras diagonales iniciales para que los paths y enlaces se resuelvan relativos a su padre, lo que hace que los renombramientos sean cambios de una línea, y usa to="." para enlazar a la ruta actual.

Cuando un layout posee datos que sus hijos outlet necesitan, pásalos con la prop context de Outlet y léelos con useOutletContext.

Anida para interfaz compartida; sáltalo cuando la única ventaja es una cadena de path más corta.

JunoAnida rutas para mantener la interfaz compartida en la pantalla El router coincide una rama del árbol de rutas y la renderiza de afuera hacia adentro, cada Outlet recibiendo el siguiente elemento, con layouts sin path inyectando elementos sin afectar la coincidencia. Los enlaces relativos se resuelven contra el árbol de rutas, así que .. sube una ruta incluso cuando esa ruta posee múltiples segmentos de URL; relative="path" acepta el escalado de segmentos.

El outlet context es un proveedor limitado, conveniente para datos de layout a página pero un punto de acoplamiento, así que mantén su forma pequeña y estable.

Reserva el anidamiento para interfaz compartida; un padre con un path y sin element es una señal de que la versión plana estaba bien.

Next up: Route params and location, where a single route serves many pages.