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

Slots

Las rutas paralelas se crean usando slots 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}
    </>
  )
}
export default function Layout({ children, team, analytics }) {
  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.

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.

Estado activo y navegación

Por defecto, Next.js realiza 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 (actualización 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.

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 paralelas no coincidentes

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

Al actualizar, Next.js renderizará un default.js para @analytics. Si default.js no existe, se renderizará un 404.

Además, como 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.

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')
  // ...
}
'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default function Layout({ auth }) {
  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".

Ejemplos

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}</>
}
import { checkUserRole } from '@/lib/auth'

export default function Layout({ user, admin }) {
  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>
    </>
  )
}
import Link from 'next/link'

export default function Layout({ children }) {
  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. 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 actualiza 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 inicio de sesión desde un diseño usando navegación del lado del cliente, o acceder a una página /login separada:

Diagrama de Rutas Paralelas

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

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

export default function Page() {
  return <Login />
}
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
}
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>
  )
}
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>
    </>
  )
}
import Link from 'next/link'

export default function Layout({ auth, children }) {
  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 actualizar o cargar inicialmente, navegar a /login llevará al usuario a la página principal de inicio de sesión.

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>
    </>
  )
}
'use client'

import { useRouter } from 'next/navigation'

export function Modal({ children }) {
  const router = useRouter()

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

Cuando usas el componente Link para navegar fuera de una página que no debería renderizar el slot @auth más, usamos una ruta catch-all que devuelve null.

import Link from 'next/link'

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

export function Modal({ children }) {
  return (
    <>
      <Link href="/">Cerrar modal</Link>
      <div>{children}</div>
    </>
  )
}
export default function CatchAll() {
  return null
}
export default function CatchAll() {
  return null
}

Nota importante:

  • Usamos una ruta catch-all en nuestro slot @auth para cerrar el modal debido al comportamiento descrito en Estado activo y navegación. 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 de error y carga personalizados

Consulta la documentación de UI de carga y Manejo de errores para más información.