Next.js: rutas avanzadas en la práctica — grupos, layouts anidados, rutas paralelas e interceptadas

La semana pasada asumí un proyecto e-commerce en Next.js con dos años en producción. Al abrir app, más de 60 carpetas apretadas en pantalla: about, products, admin-users, marketing-campaign, shop-cart… todo en un solo nivel. ¿Buscar una página de usuarios? Hay que rebuscar entre páginas de marketing. Peor aún: tres personas tocando rutas a la vez, al menos dos conflictos de Git al día; en code review, media hora solo para entender qué archivo hace qué.
Sentado frente al monitor, recordé que Next.js ya tenía grupos de rutas, rutas paralelas y más. Revisé la documentación: existían desde Next.js 13, pero el proyecto no los usaba. No faltaban herramientas; faltaba saber cuándo aplicar cada una.
Esa noche invertí tres horas en estudiarlas y dos días en reestructurar. El directorio quedó limpio, el equipo dejó de gritar «otro conflicto» en el chat. Sobre todo, entendí para qué sirven estas cuatro piezas que al principio parecen enrevesadas: grupos de rutas para ordenar carpetas, layouts anidados para reutilizar estructura, rutas paralelas para mostrar varias vistas a la vez e rutas interceptadas para modales elegantes.
Si tu proyecto Next.js crece, los archivos se amontonan y el equipo choca en Git, al terminar este artículo sabrás cuándo usar cada técnica, cómo implementarla y qué trampas evitar.
Grupos de rutas (Route Groups): directorios ordenados
¿Qué son los grupos de rutas?
En resumen: carpetas entre paréntesis, como (marketing) o (shop). Next.js no añade ese nombre a la URL.
¿Parece poco útil? Espera a un escenario real.
Imagina un e-commerce con páginas de marketing (inicio, about), tienda (listado, carrito) y panel (pedidos, usuarios). Lo clásico: todo en la raíz de app o forzar prefijos /marketing, /shop, /admin en la URL. ¿Quién quiere yoursite.com/marketing/about?
Los grupos de rutas resuelven el dilema: organizas archivos por función sin cambiar lo que ve el usuario.
Tres usos clave (todos prácticos)
Uso 1: organizar por equipo o función
El beneficio más directo. De 60 carpetas planas a tres grupos:
app/
├── (marketing)/ # Equipo de marketing
│ ├── page.js # Inicio → yoursite.com/
│ ├── about/ # About → yoursite.com/about
│ └── pricing/ # Precios → yoursite.com/pricing
├── (shop)/ # Equipo frontend
│ ├── products/ # Productos → yoursite.com/products
│ └── cart/ # Carrito → yoursite.com/cart
└── (dashboard)/ # Equipo backend
├── orders/ # Pedidos → yoursite.com/orders
└── users/ # Usuarios → yoursite.com/users
La URL sigue limpia; la estructura de archivos es obvia. Un becario entiende de un vistazo qué carpeta toca qué.
Uso 2: layouts raíz distintos por zona
Aquí brilla el grupo de rutas. ¿La barra de marketing y la del panel pueden ser iguales? No. Pero /about y /orders empiezan en la raíz. ¿Cómo darles layouts distintos?
Respuesta: cada grupo puede tener su layout.js.
app/
├── (marketing)/
│ ├── layout.js # Marketing: nav superior + hero
│ └── ...
├── (shop)/
│ ├── layout.js # Tienda: icono carrito + filtros
│ └── ...
└── (dashboard)/
├── layout.js # Panel: sidebar + control de permisos
└── ...
Tres layouts, sin interferencias. Hero en marketing, sidebar en panel, contador de carrito en tienda — un solo proyecto, sin subdominios ni varias instancias de Next.js.
Uso 3: compartir layout de forma selectiva
A veces «estas páginas comparten layout, otras no». Los artículos del blog llevan índice lateral; la portada del blog, no. Con un grupo:
app/
├── blog/
│ ├── page.js # Portada del blog, sin sidebar
│ └── (articles)/ # Artículos con sidebar compartido
│ ├── layout.js # Layout con navegación lateral
│ ├── [slug]/ # Detalle → /blog/xxx
│ └── ...
(articles) no cambia la URL: sigue siendo /blog/my-first-post, pero solo esas páginas usan el layout con navegación.
Caso real: de caos a claridad
El proyecto e-commerce con 60 carpetas. Antes y después:
Antes (extracto):
app/
├── page.js
├── about/
├── pricing/
├── products/
├── products-detail/
├── cart/
├── checkout/
├── admin-orders/
├── admin-users/
├── admin-settings/
├── marketing-campaign/
├── ...(50 más)
¿Buscar un archivo? Ctrl+F. ¿A qué módulo pertenece? Adivina por el nombre.
Después:
app/
├── (marketing)/
│ ├── layout.js
│ ├── page.js
│ ├── about/
│ ├── pricing/
│ └── campaign/
├── (shop)/
│ ├── layout.js
│ ├── products/
│ ├── cart/
│ └── checkout/
└── (dashboard)/
├── layout.js
├── orders/
├── users/
└── settings/
Tres capas, claras. Marketing en (marketing); funciones de panel en (dashboard). En code review cada equipo mira su grupo; los conflictos bajaron un 70 %.
Tres trampas a evitar
Trampa 1: conflicto de URL
Los grupos no cambian la URL. ¿Dos grupos con la misma ruta?
app/
├── (marketing)/
│ └── about/page.js # → /about
└── (shop)/
└── about/page.js # → /about(¡conflicto!)
Next.js lanza Error: Conflicting route. Solución: renombrar o añadir un segmento real (sin paréntesis):
app/
├── (marketing)/
│ └── about/page.js # → /about
└── (shop)/
└── shop-info/page.js # → /shop-info
Trampa 2: varios layouts raíz provocan recarga completa
Al ir de (shop) a (marketing) la página «parpadea». No es bug: es diseño.
Layouts raíz distintos son independientes; Next.js desmonta uno y monta otro (full page load). Si quieres transición suave, sube lo compartido a app/layout.js y deja en cada grupo solo las diferencias.
Trampa 3: ubicación de la página de inicio
Con varios grupos y cada uno con layout.js, page.js de inicio debe ir dentro de un grupo, no en app/page.js. Si no, Next.js no sabe qué layout raíz usar.
Lo habitual: inicio en (marketing)/page.js.
En una frase: organizan archivos, no tocan la URL y permiten layouts distintos por zona. Proyectos pequeños no lo necesitan; con más de 20 carpetas en app, merece la pena probar.
Layouts anidados (Nested Layouts): reutilizar estructura de página
¿Qué problema resuelven?
Inicio: solo nav superior. Listado del blog: nav + sidebar de categorías. Artículo: nav + sidebar + índice lateral.
Con componentes sueltos, montas todo en cada página. ¿Cambias el nav? Tres sitios.
Los layouts anidados de Next.js hacen que cada carpeta pueda envolver a las hijas con su layout, como muñecas rusas. Cada nivel añade UI sin repetir la exterior.
¿Cómo funcionan?
Cada carpeta puede tener layout.js; las hijas heredan y añaden otra capa.
Ejemplo: plataforma de cursos online:
app/
├── layout.js # Raíz: nav + Footer
└── courses/
├── layout.js # Cursos: raíz + categorías laterales
├── page.js # Listado de cursos
└── [id]/
├── layout.js # Detalle: + barra de progreso
└── page.js # Curso concreto
En /courses/123:
- Exterior:
app/layout.js(nav + Footer) - Medio:
courses/layout.js(sidebar de categorías) - Interior:
courses/[id]/layout.js(barra de progreso) - Contenido:
courses/[id]/page.js
Cambias el nav solo en app/layout.js; afecta a todo.
Caso práctico: blog en tres capas
Requisitos:
- Todas las páginas: nav (inicio, about, contacto) + pie
- Blog: nav + filtro de categorías lateral
- Artículo: nav + categorías + índice de anclas
Con layouts anidados queda natural:
app/
├── layout.js # Capa 1: sitio
│ └── <Header /><Footer />
└── blog/
├── layout.js # Capa 2: blog
│ └── <Sidebar />
├── page.js # Listado (hereda 1 y 2)
└── [slug]/
├── layout.js # Capa 3: artículo
│ └── <TableOfContents />
└── page.js # Detalle (hereda las tres)
Código simplificado:
app/layout.js (capa 1)
export default function RootLayout({ children }) {
return (
<html>
<body>
<Header />
{children} {/* Renderiza layout o página hijo */}
<Footer />
</body>
</html>
)
}
app/blog/layout.js (capa 2)
export default function BlogLayout({ children }) {
return (
<div className="blog-container">
<Sidebar />
<main>{children}</main>
</div>
)
}
app/blog/[slug]/layout.js (capa 3)
export default function ArticleLayout({ children }) {
return (
<div className="article-container">
{children}
<TableOfContents />
</div>
)
}
Cada layout solo añade su UI; Next.js anida el resto automáticamente.
Combinados con grupos de rutas
Los grupos aislan en horizontal (marketing, tienda, panel); los layouts apilan en vertical.
En la zona tienda del e-commerce:
app/
└── (shop)/
├── layout.js # Tienda: carrito + nav de categorías
├── products/
│ ├── layout.js # Listado: + filtros laterales
│ ├── page.js
│ └── [id]/
│ ├── layout.js # Detalle: + migas de pan
│ └── page.js
└── cart/
└── page.js # Solo layout raíz de tienda
/products: raíz tienda + filtros/products/123: raíz + filtros + migas/cart: solo raíz de tienda
Qué UI lleva cada página lo decide la jerarquía de carpetas, no if (esDetalle).
Dos detalles importantes
Detalle 1: el layout no se vuelve a renderizar
De /blog a /blog/my-post, app/layout.js y app/blog/layout.js no se re-renderizan: scroll y estado del sidebar se conservan; solo cambia el page.js interior.
Detalle 2: el layout no recibe params de rutas hijas
En app/products/[id]/page.js, [id] solo está en page.js vía params, no en layout.js.
Si el layout necesita el id (p. ej. título del producto), usa Context o sube los datos.
La idea: la estructura de carpetas refleja la jerarquía visual. Sin copiar componentes; un layout.js actualiza toda una zona.
Rutas paralelas (Parallel Routes): varias páginas a la vez
¿Cuándo las necesitas?
Panel con tres módulos simultáneos:
- Arriba izquierda: gráfico de ventas
- Arriba derecha: pedidos recientes
- Abajo: alertas de stock
Datos independientes, velocidades distintas. Lo clásico: tres fetch en dashboard/page.js; un fallo tumba todo; loading manual por módulo.
Rutas paralelas renderizan varios fragmentos de página (slots) en la misma vista, cada uno con loading, error y navegación propios.
Sintaxis: @ define el slot
Carpetas @nombre son slots:
app/
└── dashboard/
├── layout.js # Recibe slots como props
├── @sales/ # Slot 1: ventas
│ └── page.js
├── @orders/ # Slot 2: pedidos
│ └── page.js
├── @inventory/ # Slot 3: inventario
│ └── page.js
└── page.js # Contenido principal (opcional)
En layout.js, Next.js pasa los slots como props:
export default function DashboardLayout({
children, // page.js
sales, // @sales/page.js
orders, // @orders/page.js
inventory // @inventory/page.js
}) {
return (
<div className="dashboard">
<div className="widgets">
<div className="widget">{sales}</div>
<div className="widget">{orders}</div>
</div>
<div className="main">{children}</div>
<div className="alerts">{inventory}</div>
</div>
)
}
Tres «subpáginas» que colocas donde quieras, sin un page.js monolítico.
Caso práctico: panel de administración
@sales/page.js
async function getSalesData() {
const res = await fetch('https://api.example.com/sales')
return res.json()
}
export default async function SalesWidget() {
const data = await getSalesData()
return (
<div>
<h3>Ventas del mes</h3>
<Chart data={data} />
</div>
)
}
@orders/page.js
async function getRecentOrders() {
const res = await fetch('https://api.example.com/orders')
return res.json()
}
export default async function OrdersWidget() {
const orders = await getRecentOrders()
return (
<div>
<h3>Pedidos recientes</h3>
<ul>
{orders.map(order => <li key={order.id}>{order.title}</li>)}
</ul>
</div>
)
}
@inventory/page.js
export default function InventoryWidget() {
return (
<div>
<h3>Alertas de stock</h3>
<p>5 productos con stock bajo</p>
</div>
)
}
Los tres slots cargan en paralelo. Pedidos rápido → se pinta primero; ventas lento → espera. Un slot falla → los demás siguen.
loading.js y error.js por slot
app/
└── dashboard/
├── @sales/
│ ├── page.js
│ ├── loading.js
│ └── error.js
├── @orders/
│ ├── page.js
│ └── loading.js
└── @inventory/
└── page.js
@sales/loading.js:
export default function SalesLoading() {
return <div>Cargando datos de ventas...</div>
}
@sales/error.js:
'use client'
export default function SalesError({ error, reset }) {
return (
<div>
<p>Error al cargar ventas</p>
<button onClick={reset}>Reintentar</button>
</div>
)
}
Ventas en «cargando», pedidos ya visibles, inventario al instante. Si la API de ventas falla, solo ese widget muestra error.
default.js
En /dashboard navegas a /dashboard/settings. Los slots @sales, @orders no coinciden con la nueva ruta.
Por defecto Next.js mantiene el contenido anterior. Para vaciar el slot al cambiar de ruta, usa default.js:
app/
└── dashboard/
├── @sales/
│ ├── page.js
│ └── default.js
└── ...
@sales/default.js:
export default function SalesDefault() {
return null
}
En /dashboard/settings, @sales renderiza null.
El uso más común junto a rutas interceptadas para modales (siguiente sección). También brillan en dashboards con módulos independientes.
Rutas interceptadas (Intercepting Routes): modales sin dolor
La experiencia de Instagram
En Instagram o redes similares: tocas una foto en el feed, abre un modal y la URL pasa a /photo/abc123.
- Atrás en el navegador: cierra el modal, vuelves al feed
- Recargas: página completa de la foto, sin modal
- Compartes la URL: el otro ve la página completa
Lo clásico implica mucho estado, parsing de URL y history manual.
Rutas interceptadas interceptan la navegación en cliente y muestran el destino en modal; con acceso directo o recarga, renderizan la página completa.
Sintaxis: (.) y variantes
(.)— mismo nivel(..)— nivel superior(..)(..)— dos niveles arriba(...)— desde la raíz
Lista en /photos, detalle en /photos/123. Quieres modal al clic y página completa al recargar:
app/
├── @modal/
│ ├── (.)photos/
│ │ └── [id]/
│ │ └── page.js # Contenido del modal
│ └── default.js
├── layout.js
├── page.js # Feed
└── photos/
└── [id]/
└── page.js # Página completa
@modal/(.)photos/ intercepta photos al mismo nivel que @modal bajo app/.
Caso completo: modal de fotos estilo Instagram
- Grid en inicio
- Clic → modal, URL
/photos/123 - Recarga o URL directa → página completa
- Atrás cierra el modal
Estructura
app/
├── @modal/
│ ├── (.)photos/
│ │ └── [id]/
│ │ └── page.js
│ └── default.js
├── layout.js
├── page.js
└── photos/
└── [id]/
└── page.js
app/layout.js:
export default function RootLayout({ children, modal }) {
return (
<html>
<body>
{children}
{modal}
</body>
</html>
)
}
app/page.js:
import Link from 'next/link'
const photos = [
{ id: '1', url: '/images/photo1.jpg' },
{ id: '2', url: '/images/photo2.jpg' },
// ...
]
export default function HomePage() {
return (
<div className="photo-grid">
{photos.map(photo => (
<Link key={photo.id} href={`/photos/${photo.id}`}>
<img src={photo.url} alt="" />
</Link>
))}
</div>
)
}
app/@modal/(.)photos/[id]/page.js:
'use client'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
export default function PhotoModal({ params }) {
const router = useRouter()
return (
<div className="modal-backdrop" onClick={() => router.back()}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
<button onClick={() => router.back()}>Cerrar</button>
<Image src={`/images/photo${params.id}.jpg`} fill />
</div>
</div>
)
}
app/photos/[id]/page.js:
import Image from 'next/image'
export default function PhotoPage({ params }) {
return (
<div className="photo-page">
<nav>Volver al inicio</nav>
<h1>Detalle de la imagen</h1>
<Image src={`/images/photo${params.id}.jpg`} width={800} height={600} />
<p>Descripción...</p>
</div>
)
}
app/@modal/default.js:
export default function Default() {
return null
}
Comportamiento
- Clic en inicio: navegación cliente a
/photos/1→ interceptada → modal sobre el feed; URL actualizada sin recarga total. - Atrás:
router.back()→/→@modalrenderizadefault.js→ modal desaparece. - Recarga o
/photos/1directo: sin interceptación →photos/[id]/page.jscompleto. - Enlace compartido: página completa para quien abre la URL.
URL compartible, atrás cierra modal, recarga muestra página entera.
Elegir el nivel de interceptación
Con interceptación en app/@modal/:
- Objetivo
app/photos/(mismo nivel) →(.)photos - Objetivo
app/shop/products/→(..) - Cualquier profundidad →
(...)
Ejemplo:
app/
└── shop/
├── @modal/
│ └── (..)products/
│ └── [id]/
└── products/
└── [id]/
@modal está en shop/; products es hijo de shop → (..).
Consejo: empieza con (...); cuando funcione, afina a (.) o (..).
Tres trampas
Trampa 1: sin default.js el modal no se cierra
Sin default.js, el slot conserva el modal. Añade default.js con null.
Trampa 2: useRouter en Server Component
El modal suele usar useRouter().back() → 'use client' obligatorio.
Trampa 3: anidación profunda
Con rutas como app/shop/(store)/products/[id], calcula bien la ruta. Si te pierdes, (...) desde la raíz es menos elegante pero seguro.
Interceptadas + paralelas cubren modales con URL compartible, recarga con página completa, atrás cierra y adelante reabre. Instagram, Twitter y Airbnb usan este patrón; en Next.js también puedes.
Práctica integrada: las cuatro técnicas juntas
Escenario: e-commerce completo
La fuerza está en combinarlas. Requisitos de un e-commerce mediano:
Tres zonas (layouts distintos):
- Marketing (
/,/about,/pricing): hero + nav simple - Tienda (
/products,/cart): carrito fijo + categorías - Panel (
/dashboard): sidebar + permisos
Tienda:
- Listado con filtros laterales
- Detalle con migas de pan
- Clic en tarjeta → vista rápida en modal sin salir del listado
- Recarga o URL directa → detalle completo
Panel:
- Ventas, pedidos e inventario en paralelo, cada uno con loading y error propios
Estructura de directorios
app/
├── layout.js
│
├── (marketing)/
│ ├── layout.js
│ ├── page.js
│ ├── about/
│ └── pricing/
│
├── (shop)/
│ ├── layout.js
│ ├── @modal/
│ │ ├── (.)products/
│ │ │ └── [id]/
│ │ │ └── page.js
│ │ └── default.js
│ │
│ ├── products/
│ │ ├── layout.js
│ │ ├── page.js
│ │ └── [id]/
│ │ ├── layout.js
│ │ └── page.js
│ │
│ └── cart/
│ └── page.js
│
└── (dashboard)/
├── layout.js
├── @sales/
│ ├── page.js
│ └── loading.js
├── @orders/
│ ├── page.js
│ └── loading.js
├── @inventory/
│ └── page.js
└── page.js
- Grupos separan las tres zonas
- Layouts anidados añaden UI en tienda
- Paralelas: modal en tienda, módulos en panel
- Interceptadas: vista rápida de producto
Código clave
app/(shop)/layout.js:
export default function ShopLayout({ children, modal }) {
return (
<div>
<nav>{/* Carrito + categorías */}</nav>
{children}
{modal}
</div>
)
}
app/(shop)/products/layout.js:
export default function ProductsLayout({ children }) {
return (
<div className="products-container">
<aside>{/* Filtros */}</aside>
<main>{children}</main>
</div>
)
}
app/(shop)/products/page.js:
import Link from 'next/link'
export default function ProductsPage() {
return (
<div className="product-grid">
{products.map(p => (
<Link key={p.id} href={`/products/${p.id}`}>
<ProductCard product={p} />
</Link>
))}
</div>
)
}
Clic → navegación cliente → interceptación → modal.
app/(shop)/@modal/(.)products/[id]/page.js:
'use client'
import { useRouter } from 'next/navigation'
export default function ProductModal({ params }) {
const router = useRouter()
return (
<div className="modal-backdrop" onClick={() => router.back()}>
<div className="modal">
<h2>Vista rápida del producto</h2>
<ProductPreview id={params.id} />
<Link href={`/products/${params.id}`} onClick={() => router.back()}>
Ver detalle completo
</Link>
</div>
</div>
)
}
app/(dashboard)/layout.js:
export default function DashboardLayout({ children, sales, orders, inventory }) {
return (
<div className="dashboard">
<aside>{/* Sidebar */}</aside>
<main>
{children}
<div className="widgets">
<div className="widget">{sales}</div>
<div className="widget">{orders}</div>
<div className="widget">{inventory}</div>
</div>
</main>
</div>
)
}
Por qué esta arquitectura
Grupos de rutas: nav distinta por zona; equipos separados, menos conflictos.
Layouts anidados: filtros en listado, migas en detalle, nav de tienda compartida; carpetas = capas de UI.
Interceptadas + paralelas en modales: vista rápida sin abandonar el listado; URL /products/123 para compartir y SEO; recarga = página completa.
Paralelas en panel: APIs y tiempos distintos; fallos aislados por módulo.
Beneficios en equipo
Tras la reestructuración:
- Conflictos −65 %: frontend en
(shop), backend en(dashboard) - Onboarding más rápido: el árbol de carpetas explica el dominio
- Mantenimiento: un
layout.jspor zona, sin efectos colaterales - Conversión +23 % en vista rápida vs. salto a detalle (menos fricción al volver)
Estas cuatro técnicas no son postureo: estructura más clara, equipo más fluido, UX mejor. Proyectos pequeños no hace falta forzarlas; con decenas de rutas, varios equipos y modales complejos, este esquema ayuda de verdad.
Conclusión
El proyecto de 60 carpetas desordenadas, tras reestructurar, dejó de consumir tiempo en buscar archivos, resolver conflictos y pelear con layouts. Puedes centrarte en la lógica de negocio.
Resumen:
| Técnica | Función | Cuándo usarla | Sintaxis |
|---|---|---|---|
| Grupos de rutas | Organizar archivos, aislar layouts | Varias zonas, varios equipos | (folderName) |
| Layouts anidados | Apilar UI por capas | Nav multinivel | layout.js en cada nivel |
| Rutas paralelas | Varios fragmentos a la vez | Panel, módulos independientes | @folderName |
| Rutas interceptadas | Interceptar nav, modales | Modales estilo Instagram | (.) (..) (...) |
No uses todo de golpe. Empieza por grupos de rutas; layouts anidados cuando la UI se apila; paralelas e interceptadas para panel y modales.
La primera lectura de la documentación confunde; después de usarlas una vez, encajan: carpetas = rutas, jerarquía de archivos = jerarquía de UI, interceptación = experiencia.
Si tu Next.js ya se siente abultado, dedica medio día a reagrupar rutas. En tres meses probablemente te lo agradezcas.
Flujo completo de reestructuración con rutas avanzadas de Next.js
Pasa de una estructura caótica a una arquitectura clara con grupos de rutas, layouts anidados, rutas paralelas e interceptadas
⏱️ Estimated time: 4 hr
- 1
Step 1: Analizar la estructura actual del proyecto
Evalúa la cantidad y complejidad de rutas:
• Cuenta carpetas bajo app (más de 20 → conviene grupos de rutas)
• Identifica zonas funcionales (marketing, tienda, panel, etc.)
• Localiza grupos de páginas que necesitan layouts distintos
• Registra los puntos de conflicto del equipo
Criterios:
• Más de 20 carpetas: grupos de rutas
• Navegación multinivel: layouts anidados
• Varios módulos independientes a la vez: rutas paralelas
• Interacción con modales: rutas interceptadas + paralelas - 2
Step 2: Crear grupos de rutas por zona funcional
Usa paréntesis para agrupar por función o equipo:
• Grupo (marketing): inicio, about, pricing
• Grupo (shop): productos, carrito
• Grupo (dashboard): pedidos, usuarios
Notas:
• El nombre del grupo no afecta la URL, pero la misma URL no puede estar en varios grupos
• Cada grupo puede tener su layout.js
• page.js de inicio debe ir dentro de un grupo (no en app/page.js) - 3
Step 3: Diseñar la jerarquía de layouts anidados
Diseña según la jerarquía de UI:
• Nivel 1: app/layout.js (global: Header + Footer)
• Nivel 2: layout.js de zona (p. ej. blog/layout.js con sidebar)
• Nivel 3: layout.js de detalle (p. ej. blog/[slug]/layout.js con índice)
Puntos clave:
• Cada layout solo añade UI propia de ese nivel
• Los layouts hijos heredan del padre
• El layout no se vuelve a renderizar; buen rendimiento - 4
Step 4: Implementar rutas paralelas (si hace falta)
Crea slots con @:
• @modal: para modales
• @sales, @orders: módulos independientes del panel
Recíbelos en layout.js:
• export default function Layout({ children, modal, sales, orders })
• En JSX: {modal} {sales} {orders}
Cada slot puede tener su loading.js y error.js - 5
Step 5: Implementar rutas interceptadas (si necesitas modales)
Crea la estructura de interceptación:
• En @modal: (.)photos/[id]/page.js (intercepta ruta del mismo nivel)
• photos/[id]/page.js (página completa)
Sintaxis:
• (.): intercepta rutas del mismo nivel
• (..): intercepta el nivel superior
• (...): intercepta desde la raíz
Debes crear default.js que devuelva null para poder cerrar el modal - 6
Step 6: Probar y validar
Comprueba todo:
• Grupos de rutas: URL sin cambios, estructura clara
• Layouts anidados: jerarquía de UI correcta, estado conservado
• Rutas paralelas: carga independiente, errores aislados
• Rutas interceptadas: modal en navegación cliente, página completa al recargar
Rendimiento:
• Usa Next.js DevTools para contar renders de layout
• Confirma que el layout no se vuelve a renderizar al cambiar subrutas
FAQ
¿Los grupos de rutas afectan la URL?
¿Cuándo debería usar grupos de rutas?
¿Los layouts anidados afectan el rendimiento?
¿En qué se diferencian las rutas paralelas de un componente normal?
¿Cómo elegir la sintaxis (.) (..) (...) de rutas interceptadas?
¿Por qué las rutas interceptadas necesitan default.js?
¿Hay que reestructurar rutas al migrar de Pages Router a App Router?
15 min de lectura · Publicado el: 18 dic 2025 · Actualizado el: 21 ago 2026
Guía completa de Next.js
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Guía práctica de migración de Next.js Pages Router a App Router: estrategia gradual y lista de errores comunes
¿Quieres migrar de Next.js Pages Router a App Router de cero? Esta guía ofrece un plan de migración gradual, soluciones a 7 problemas frecuentes y experiencia real de producción para actualizar a Next.js 14 con seguridad.
Parte 3 de 51
Siguiente
Next.js App Router en la práctica: grupos de rutas y layouts anidados para proyectos grandes
Con grupos de rutas, layouts anidados, rutas paralelas e interceptadas, resuelve el caos de directorios, los conflictos de rutas y los problemas de colaboración en proyectos Next.js grandes, con una estructura de carpetas lista para usar.
Parte 5 de 51



Comentarios
Inicia sesión con GitHub para dejar un comentario