Rutas Paralelas

Las Rutas Paralelas te permiten renderizar simultáneamente o condicionalmente una o más páginas dentro del mismo diseño. Son útiles para secciones altamente dinámicas de una aplicación, como paneles de control y feeds en sitios sociales.

Por ejemplo, considerando un panel de control, puedes usar rutas paralelas para renderizar simultáneamente las páginas team y analytics:

Diagrama de Rutas Paralelas

Convención

Slots (Ranuras)

Las rutas paralelas se crean usando slots (ranuras) con nombre. Los slots se definen con la convención @folder. Por ejemplo, la siguiente estructura de archivos define dos slots: @analytics y @team:

Estructura de archivos de Rutas Paralelas

Los slots se pasan como props al diseño padre compartido. Para el ejemplo anterior, el componente en app/layout.js ahora acepta los props de slots @analytics y @team, y puede renderizarlos en paralelo junto con el prop children:

export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  )
}

Sin embargo, los slots no son segmentos de ruta y no afectan la estructura de la URL. Por ejemplo, para /@analytics/views, la URL será /views ya que @analytics es un slot. Los slots se combinan con el componente Page regular para formar la página final asociada con el segmento de ruta. Debido a esto, no puedes tener slots estáticos y dinámicos separados en el mismo nivel de segmento de ruta. Si un slot es dinámico, todos los slots en ese nivel deben ser dinámicos.

Nota importante:

  • El prop children es un slot implícito que no necesita mapearse a una carpeta. Esto significa que app/page.js es equivalente a app/@children/page.js.

default.js

Puedes definir un archivo default.js para renderizar como respaldo para slots no coincidentes durante la carga inicial o recarga completa de página.

Considera la siguiente estructura de carpetas. El slot @team tiene una página /settings, pero @analytics no.

Rutas no coincidentes en Rutas Paralelas

Al navegar a /settings, el slot @team renderizará la página /settings mientras mantiene la página activa actual para el slot @analytics.

Al refrescar, Next.js renderizará un default.js para @analytics. Si default.js no existe, se renderizará un 404 en su lugar.

Además, dado que children es un slot implícito, también necesitas crear un archivo default.js para renderizar un respaldo para children cuando Next.js no pueda recuperar el estado activo de la página padre.

Comportamiento

Por defecto, Next.js mantiene un seguimiento del estado activo (o subpágina) para cada slot. Sin embargo, el contenido renderizado dentro de un slot dependerá del tipo de navegación:

  • Navegación Suave: Durante la navegación del lado del cliente, Next.js realizará un renderizado parcial, cambiando la subpágina dentro del slot, mientras mantiene las subpáginas activas de los otros slots, incluso si no coinciden con la URL actual.
  • Navegación Dura: Después de una carga completa de página (refresco del navegador), Next.js no puede determinar el estado activo para los slots que no coinciden con la URL actual. En su lugar, renderizará un archivo default.js para los slots no coincidentes, o 404 si default.js no existe.

Nota importante:

  • El 404 para rutas no coincidentes ayuda a garantizar que no renderices accidentalmente una ruta paralela en una página para la que no estaba destinada.

Ejemplos

Con useSelectedLayoutSegment(s)

Tanto useSelectedLayoutSegment como useSelectedLayoutSegments aceptan un parámetro parallelRoutesKey, que te permite leer el segmento de ruta activo dentro de un slot.

'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default function Layout({ auth }: { auth: React.ReactNode }) {
  const loginSegment = useSelectedLayoutSegment('auth')
  // ...
}

Cuando un usuario navega a app/@auth/login (o /login en la barra de URL), loginSegment será igual a la cadena "login".

Rutas Condicionales

Puedes usar Rutas Paralelas para renderizar condicionalmente rutas basadas en ciertas condiciones, como el rol del usuario. Por ejemplo, para renderizar una página de panel de control diferente para los roles /admin o /user:

Diagrama de rutas condicionales
import { checkUserRole } from '@/lib/auth'

export default function Layout({
  user,
  admin,
}: {
  user: React.ReactNode
  admin: React.ReactNode
}) {
  const role = checkUserRole()
  return role === 'admin' ? admin : user
}

Grupos de Pestañas

Puedes agregar un layout dentro de un slot para permitir que los usuarios naveguen el slot de forma independiente. Esto es útil para crear pestañas.

Por ejemplo, el slot @analytics tiene dos subpáginas: /page-views y /visitors.

Slot analytics con dos subpáginas y un layout

Dentro de @analytics, crea un archivo layout para compartir las pestañas entre las dos páginas:

import Link from 'next/link'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Link href="/page-views">Page Views</Link>
        <Link href="/visitors">Visitors</Link>
      </nav>
      <div>{children}</div>
    </>
  )
}

Modales

Las Rutas Paralelas pueden usarse junto con Rutas de Interceptación para crear modales que admitan enlaces profundos. Esto te permite resolver desafíos comunes al construir modales, como:

  • Hacer que el contenido del modal sea compartible a través de una URL.
  • Preservar el contexto cuando se refresca la página, en lugar de cerrar el modal.
  • Cerrar el modal en la navegación hacia atrás en lugar de ir a la ruta anterior.
  • Reabrir el modal en la navegación hacia adelante.

Considera el siguiente patrón de UI, donde un usuario puede abrir un modal de login desde un diseño usando navegación del lado del cliente, o acceder a una página separada /login:

Diagrama de Rutas Paralelas

Para implementar este patrón, comienza creando una ruta /login que renderice tu página principal de login.

Diagrama de Rutas Paralelas
import { Login } from '@/app/ui/login'

export default function Page() {
  return <Login />
}

Luego, dentro del slot @auth, agrega un archivo default.js que devuelva null. Esto asegura que el modal no se renderice cuando no está activo.

export default function Default() {
  return null
}

Dentro de tu slot @auth, intercepta la ruta /login actualizando la carpeta /(.)login. Importa el componente <Modal> y sus hijos al archivo /(.)login/page.tsx:

import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'

export default function Page() {
  return (
    <Modal>
      <Login />
    </Modal>
  )
}

Nota importante:

Abriendo el modal

Ahora, puedes aprovechar el enrutador de Next.js para abrir y cerrar el modal. Esto asegura que la URL se actualice correctamente cuando el modal está abierto, y al navegar hacia atrás y adelante.

Para abrir el modal, pasa el slot @auth como prop al diseño padre y renderízalo junto con el prop children.

import Link from 'next/link'

export default function Layout({
  auth,
  children,
}: {
  auth: React.ReactNode
  children: React.ReactNode
}) {
  return (
    <>
      <nav>
        <Link href="/login">Abrir modal</Link>
      </nav>
      <div>{auth}</div>
      <div>{children}</div>
    </>
  )
}

Cuando el usuario haga clic en el <Link>, el modal se abrirá en lugar de navegar a la página /login. Sin embargo, al refrescar o cargar inicialmente, navegar a /login llevará al usuario a la página principal de login.

Cerrando el modal

Puedes cerrar el modal llamando a router.back() o usando el componente Link.

'use client'

import { useRouter } from 'next/navigation'

export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter()

  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        Cerrar modal
      </button>
      <div>{children}</div>
    </>
  )
}

Cuando uses el componente Link para navegar fuera de una página que no debería renderizar el slot @auth más, necesitamos asegurarnos de que la ruta paralela coincida con un componente que devuelva null. Por ejemplo, al navegar de vuelta a la página raíz, creamos un componente @auth/page.tsx:

import Link from 'next/link'

export function Modal({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Link href="/">Cerrar modal</Link>
      <div>{children}</div>
    </>
  )
}

O si navegas a cualquier otra página (como /foo, /foo/bar, etc), puedes usar un slot catch-all:

export default function CatchAll() {
  return null
}

Nota importante:

  • Usamos una ruta catch-all en nuestro slot @auth para cerrar el modal debido a cómo se comportan las rutas paralelas(#behavior). Dado que las navegaciones del lado del cliente a una ruta que ya no coincide con el slot permanecerán visibles, necesitamos hacer coincidir el slot con una ruta que devuelva null para cerrar el modal.
  • Otros ejemplos podrían incluir abrir un modal de foto en una galería mientras también tienes una página dedicada /photo/[id], o abrir un carrito de compras en un modal lateral.
  • Ver un ejemplo de modales con Rutas Interceptadas y Paralelas.

UI de Carga y Error

Las Rutas Paralelas pueden transmitirse de forma independiente, permitiéndote definir estados de error y carga independientes para cada ruta:

Las rutas paralelas permiten estados personalizados de error y carga

Consulta la documentación de UI de Carga y Manejo de Errores para más información.

On this page