Las aplicaciones web modernas con alta concurrencia demandan escenarios sofisticados y dinámicos para el renderizado de interfaces:
loading.tsx) y aislar fallos de ejecución (error.tsx) sin degradar los paneles adyacentes./photo/123). Simultáneamente, el acceso directo a dicho enlace o una recarga forzada (F5) deben renderizar una página completa independiente y no una ventana modal.Next.js App Router resuelve estos desafíos a nivel de núcleo mediante rutas paralelas (Parallel Routes) y rutas de intercepción (Intercepting Routes).
@slot)#Las rutas paralelas se definen mediante carpetas con el prefijo @ (slots o ranuras). Los nombres de los slots no forman parte de la URL pública (app/dashboard/@analytics compila a la URL /dashboard), sino que se inyectan como props de React con nombre en el componente layout.tsx del nivel correspondiente.
El archivo principal page.tsx actúa implícitamente como el slot predeterminado children (equivalente a @children). Cada slot paralelo constituye una rama independiente del árbol de React con su propio ciclo de vida, límite de Suspense y ámbito de caché.
(.), (..))#Las rutas de intercepción permiten mapear la navegación del cliente por enlaces a un componente alternativo, manteniendo el contexto de la vista actual:
(.) — intercepta rutas en el mismo nivel de segmento.(..) — intercepta rutas un nivel de segmento por encima.(..)(..) — intercepta rutas dos niveles de segmento por encima.(...) — intercepta rutas desde la raíz del directorio app.Importante: Las carpetas de slots paralelos (
@slot) y los grupos de rutas ((group)) no son segmentos de URL y se ignoran por completo al calcular los niveles de retroceso para(..).
El usuario está en /feed y hace clic en <Link href='/photo/42' scroll={false}>.
Turbopack identifica el interceptor app/feed/@modal/(..)photo/[id]/page.tsx en lugar de la página normal.
El Layout renderiza children de fondo (/feed) e inyecta el modal en el slot @modal. La URL cambia a /photo/42.
El usuario presiona F5 o accede directamente mediante un enlace compartido.
El servidor renderiza la página independiente app/photo/[id]/page.tsx. El slot inactivo utiliza default.tsx (null), previniendo el error 404.
¿Qué sucede si un usuario presiona F5 en una página con una tarjeta activa en @modal en /photo/42, pero no existe el archivo default.tsx en el directorio del slot?
Los slots paralelos se reciben en layout.tsx como props tipadas de React:
interface DashboardLayoutProps {
children: React.ReactNode;
analytics: React.ReactNode; // Slot app/dashboard/@analytics
notifications: React.ReactNode; // Slot app/dashboard/@notifications
}
export default function DashboardLayout({
children,
analytics,
notifications,
}: DashboardLayoutProps) {
return (
<div className="p-6 space-y-6">
<main>{children}</main>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<section className="border border-border p-4 rounded-lg">
{analytics}
</section>
<section className="border border-border p-4 rounded-lg">
{notifications}
</section>
</div>
</div>
);
}El archivo default.tsx define qué renderizar en un slot paralelo cuando la URL actual no coincide con ninguna ruta dentro de dicho slot:
// Si el modal no está activo, retornamos null para ocultar el contenido del slot
export default function DefaultModalSlot() {
return null;
}Dada la estructura app/feed/@modal/(..)photo/[id]/page.tsx, ¿cuántos niveles de URL asciende el operador (..)?
'use client';
import { useRouter } from 'next/navigation';
import { use } from 'react';
interface PhotoModalProps {
params: Promise<{ id: string }>;
}
export default function PhotoModal({ params }: PhotoModalProps) {
const router = useRouter();
const { id } = use(params);
return (
<div className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4">
<div className="bg-bg-surface border border-border rounded-xl max-w-lg w-full p-6 space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-lg font-bold">Foto #{id}</h3>
{/* router.back() restaura la URL anterior y desmonta el modal interceptado */}
<button
onClick={() => router.back()}
className="text-text-muted hover:text-text-primary text-sm px-2 py-1 rounded bg-bg-elevated"
>
Cerrar ✕
</button>
</div>
<div className="aspect-video bg-black/40 rounded-lg flex items-center justify-center font-mono text-xs text-text-muted">
[Visualizando fotografía {id}]
</div>
</div>
</div>
);
}Al utilizar rutas paralelas (@slot), no incluir un archivo default.tsx en la raíz del slot provocará que la primera recarga dura (F5) haga fallar toda la aplicación con un error 404.
Solución: Declara siempre un archivo default.tsx (que al menos retorne return null;) en cada slot paralelo definido.
Es habitual escribir (..)(..)photo, asumiendo erróneamente que @modal cuenta como segmento de URL. Esto provoca que el interceptor nunca coincida.
Solución: Recuerda que las carpetas con prefijo @ y con paréntesis () son transparentes para el enrutador. Salir de app/feed/@modal hacia app/ requiere un único operador (..).
Al hacer clic en un enlace <Link href="/photo/123">, el navegador se desplaza por defecto a la parte superior, perdiendo la posición en la lista de fondo.
Solución: Especifica siempre el atributo scroll={false} en los enlaces que abren modales contextuales: <Link href="/photo/123" scroll={false}>.