¡Rutas Dinámicas y Anidadas en Next.js con el App Router! 🚀 Guía Completa
Este tutorial te guiará a través del potente sistema de enrutamiento dinámico y anidado de Next.js, utilizando el App Router. Aprenderás a crear rutas flexibles, pasar parámetros de URL y estructurar tus aplicaciones con layouts compartidos, mejorando la escalabilidad y la organización de tu código.
Next.js, con su App Router, ha revolucionado la forma en que construimos aplicaciones web modernas y escalables. Una de las características más poderosas y esenciales para cualquier aplicación del mundo real es la capacidad de manejar rutas dinámicas y anidadas. Esto te permite crear URLs flexibles que responden a datos específicos (como el ID de un producto o el nombre de un usuario) y organizar tu interfaz de usuario con layouts compartidos que persisten a través de diferentes segmentos de la URL.
En esta guía completa, exploraremos a fondo cómo implementar y aprovechar al máximo las rutas dinámicas y anidadas utilizando el App Router de Next.js. Desde la configuración básica hasta patrones avanzados, te proporcionaremos los conocimientos y ejemplos prácticos necesarios para construir aplicaciones robustas y eficientes.
¿Qué es el App Router en Next.js? 🤔
Antes de sumergirnos en las rutas dinámicas, es fundamental entender el contexto del App Router. Introducido en Next.js 13 y estable en Next.js 14, el App Router es un nuevo modelo de enrutamiento construido sobre React Server Components y diseñado para la flexibilidad y el rendimiento. A diferencia del antiguo pages directory, el app directory permite un control más granular sobre el renderizado (cliente vs. servidor) y facilita la creación de layouts complejos.
Ventajas clave del App Router:
- React Server Components: Renderizado en el servidor por defecto, lo que mejora el rendimiento inicial y el SEO.
- Layouts anidados: Facilita la creación de interfaces de usuario complejas con componentes compartidos entre rutas.
- Manejo de errores mejorado:
error.jsynot-found.jspara una mejor experiencia de usuario. - Streaming y carga de datos: Soporte nativo para la carga de datos asíncrona.
- Patrones de carga UI: Uso de
loading.jspara indicar estados de carga.
Rutas Dinámicas: Creando URLs Flexibles ✨
Las rutas dinámicas son la columna vertebral de cualquier aplicación que necesite mostrar contenido basado en datos variables. Imagina una tienda online: cada producto tiene su propia página, pero no quieres crear un archivo HTML o un componente para cada uno. Ahí es donde entran las rutas dinámicas.
En el App Router, las rutas dinámicas se definen utilizando corchetes [] en el nombre de la carpeta.
1. Conceptos Básicos de Rutas Dinámicas
Para crear una ruta dinámica, simplemente nombra una carpeta con corchetes. El contenido dentro de los corchetes será el parámetro de la ruta.
Por ejemplo, para una página de perfil de usuario, tendrías una estructura como esta:
// app/users/[userId]/page.js
export default function UserProfilePage({ params }) {
return (
<div>
<h1>Perfil del Usuario: {params.userId}</h1>
{/* ... más contenido del perfil ... */}
</div>
);
}
En este ejemplo, [userId] es el segmento dinámico. Si navegas a /users/123, params.userId será "123".
Accediendo a los parámetros dinámicos
El componente page.js o layout.js recibe un prop params que es un objeto con los segmentos dinámicos de la URL como propiedades. Si tienes múltiples segmentos dinámicos, todos aparecerán en este objeto.
2. Ejemplos Prácticos de Rutas Dinámicas
Vamos a construir un ejemplo sencillo para una aplicación de blog con posts dinámicos.
Estructura de archivos:
app/
├── blog/
│ └── [slug]/
│ └── page.js
└── page.js
Código para app/blog/[slug]/page.js:
// app/blog/[slug]/page.js
import { notFound } from 'next/navigation';
// Simulamos una base de datos de posts
const posts = [
{ slug: 'mi-primer-post', title: 'Mi Primer Post en Next.js', content: 'Contenido del primer post...' },
{ slug: 'tutorial-nextjs', title: 'Tutorial de Next.js Avanzado', content: 'Aquí aprenderás Next.js a fondo...' },
];
export default function BlogPostPage({ params }) {
const { slug } = params;
const post = posts.find(p => p.slug === slug);
if (!post) {
notFound(); // Muestra la página 404 de Next.js si el post no existe
}
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
<p>Este post se generó dinámicamente con el slug: <mark>{slug}</mark></p>
</main>
);
}
// Función para generar rutas estáticas durante el build (para SSG)
export async function generateStaticParams() {
return posts.map((post) => ({
slug: post.slug,
}));
}
¿Qué es `generateStaticParams()`? 🤔
`generateStaticParams()` es una función especial que Next.js ejecuta en el servidor durante el proceso de *build*. Permite pre-renderizar rutas dinámicas con antelación, lo que es ideal para contenido que no cambia con frecuencia. Para cada objeto devuelto por `generateStaticParams()`, Next.js generará una página estática para esa combinación de parámetros. Esto es esencial para la Generación Estática de Sitios (SSG) con rutas dinámicas. Si no se usa, la ruta se considerará Dinámica Server-Side (SSR) o Server-Side Rendering en cada solicitud.3. Rutas Dinámicas Catch-all [...slug] 🎣
A veces, necesitas una ruta dinámica que capture todos los segmentos restantes de la URL. Para esto, usa el patrón catch-all con [...slug]. Esto es útil para rutas como /docs/a/b/c donde a/b/c podría ser una estructura de subcarpetas.
Estructura de archivos:
app/
├── docs/
│ └── [...slug]/
│ └── page.js
└── page.js
Código para app/docs/[...slug]/page.js:
// app/docs/[...slug]/page.js
export default function DocsPage({ params }) {
// params.slug será un array de strings, e.g., ['a', 'b', 'c']
const docPath = params.slug ? params.slug.join('/') : 'Inicio';
return (
<main>
<h1>Documentación para: {docPath}</h1>
<p>Esta página captura múltiples segmentos de URL.</p>
<ul>
{params.slug.map((segment, index) => (
<li key={index}>Segmento {index + 1}: <strong>{segment}</strong></li>
))}
</ul>
</main>
);
}
export async function generateStaticParams() {
return [
{ slug: ['getting-started'] },
{ slug: ['components', 'buttons'] },
{ slug: ['components', 'forms', 'input'] },
];
}
Si accedes a /docs/components/buttons, params.slug será ['components', 'buttons'].
Rutas Anidadas y Layouts Compartidos 📂
Las rutas anidadas son la forma en que el App Router organiza y renderiza segmentos de UI basados en la estructura de carpetas. Cuando tienes rutas anidadas, puedes definir layouts que envuelvan el contenido de las rutas hijas, permitiendo que una parte de tu UI permanezca constante mientras el contenido principal cambia.
1. Creando Layouts Anidados
Un layout.js es un componente React que envuelve un page.js o cualquier layout.js hijo. Recibe un prop children que contiene el contenido de la ruta anidada.
Estructura de archivos:
app/
├── dashboard/
│ ├── analytics/
│ │ └── page.js
│ ├── settings/
│ │ └── profile/
│ │ └── page.js
│ │ └── page.js
│ └── layout.js <-- Layout para /dashboard y sus subrutas
├── page.js
└── layout.js <-- Layout Root para toda la aplicación
Código para app/layout.js (Layout Root):
// app/layout.js
import './globals.css'; // Estilos globales
export const metadata = {
title: 'Mi App Next.js con Rutas Anidadas',
description: 'Explorando layouts y rutas dinámicas.',
};
export default function RootLayout({ children }) {
return (
<html lang="es">
<body>
<header style={{ background: '#eee', padding: '10px' }}>
<h1>Cabecera Global</h1>
<nav>
<a href="/">Inicio</a> | <a href="/dashboard">Dashboard</a> | <a href="/blog/mi-primer-post">Blog Post</a>
</nav>
</header>
{children}
<footer style={{ background: '#eee', padding: '10px', marginTop: '20px' }}>
<p>© 2024 Mi Empresa</p>
</footer>
</body>
</html>
);
}
Código para app/dashboard/layout.js (Layout para Dashboard):
// app/dashboard/layout.js
export const metadata = {
title: 'Dashboard de Usuario',
};
export default function DashboardLayout({ children }) {
return (
<section style={{ border: '1px solid #ccc', padding: '20px', margin: '20px' }}>
<h2>Panel de Control</h2>
<nav>
<a href="/dashboard">Inicio Dashboard</a> | <a href="/dashboard/settings">Ajustes</a> | <a href="/dashboard/analytics">Analíticas</a>
</nav>
<div style={{ marginTop: '15px' }}>
{children} {/* Aquí se renderiza el contenido de las rutas hijas */}
</div>
</section>
);
}
Código para app/dashboard/page.js:
// app/dashboard/page.js
export default function DashboardIndexPage() {
return (
<div>
<h3>Bienvenido al Dashboard</h3>
<p>Selecciona una opción del menú lateral.</p>
</div>
);
}
Código para app/dashboard/settings/page.js:
// app/dashboard/settings/page.js
export default function SettingsPage() {
return (
<div>
<h3>Ajustes Generales</h3>
<p>Configura tus preferencias aquí.</p>
</div>
);
}
Cuando accedas a /dashboard/settings, verás:
- La cabecera y el pie de página de
app/layout.js. - El
Panel de Controly la navegación delapp/dashboard/layout.js. - El contenido de
app/dashboard/settings/page.jsdentro del<div style={{ marginTop: '15px' }}>del layout del dashboard.
2. Layouts Anidados con Rutas Dinámicas
Podemos combinar la potencia de las rutas dinámicas con los layouts anidados. Por ejemplo, un layout que se aplique a todos los perfiles de usuario, pero el contenido de cada perfil sea dinámico.
Estructura de archivos:
app/
├── users/
│ └── [userId]/
│ ├── layout.js <-- Layout específico para un usuario
│ ├── page.js
│ └── settings/
│ └── page.js
└── page.js
Código para app/users/[userId]/layout.js:
// app/users/[userId]/layout.js
import { notFound } from 'next/navigation';
// Simulamos usuarios
const users = [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
];
export default function UserLayout({ children, params }) {
const { userId } = params;
const user = users.find(u => u.id === userId);
if (!user) {
notFound();
}
return (
<div style={{ border: '2px dashed blue', padding: '15px', margin: '15px' }}>
<h2>Usuario: <span class="badge yellow">{user.name} (ID: {userId})</span></h2>
<nav>
<a href={`/users/${userId}`}>Ver Perfil</a> |
<a href={`/users/${userId}/settings`}>Ajustes del Usuario</a>
</nav>
<hr />
{children}
</div>
);
}
export async function generateStaticParams() {
return users.map((user) => ({
userId: user.id,
}));
}
Código para app/users/[userId]/page.js:
// app/users/[userId]/page.js
export default function UserProfilePage({ params }) {
return (
<div>
<h3>Información General del Perfil</h3>
<p>Aquí se mostrarían los detalles del usuario {params.userId}.</p>
</div>
);
}
Código para app/users/[userId]/settings/page.js:
// app/users/[userId]/settings/page.js
export default function UserSettingsPage({ params }) {
return (
<div>
<h3>Ajustes Específicos del Usuario</h3>
<p>Modifica la configuración de {params.userId} aquí.</p>
</div>
);
}
Ahora, si navegas a /users/1, verás el UserLayout con el nombre de Alice, y dentro, el contenido de UserProfilePage. Si navegas a /users/1/settings, el UserLayout persistirá, pero el contenido interno cambiará a UserSettingsPage.
3. Slots para UI Paralela (Advanced) 🧩
El App Router también permite la UI paralela mediante slots con @folderName. Esto es útil para renderizar una o más rutas independientes en la misma vista, por ejemplo, dashboards con múltiples paneles que pueden cargar su propio estado de forma asíncrona.
Aunque es un tema más avanzado, es bueno saber que existe. Los slots se definen con @folderName y se pasan como props al layout.js que los contiene.
app/
├── dashboard/
│ ├── @team/
│ │ └── page.js
│ ├── @analytics/
│ │ └── page.js
│ ├── layout.js
│ └── page.js
En app/dashboard/layout.js recibirías @team y @analytics como props, además de children:
export default function DashboardLayout({ children, team, analytics }) {
return (
<section>
{children}
{team} {/* Renderiza el slot del equipo */}
{analytics} {/* Renderiza el slot de analíticas */}
</section>
);
}
Navegación y Enlaces 🔗
Para navegar entre rutas dinámicas y anidadas, Next.js proporciona el componente Link de next/link y el hook useRouter de next/navigation.
1. Usando <Link>
next/link es la forma recomendada para la navegación del lado del cliente. Permite pre-fetch de las rutas en segundo plano para una navegación instantánea.
import Link from 'next/link';
export default function HomePage() {
const userId = '3';
const postId = 'tutorial-nextjs';
return (
<div>
<h1>Inicio</h1>
<nav>
<ul>
<li><Link href="/dashboard">Ir al Dashboard</Link></li>
<li><Link href={`/users/${userId}`}>Ver perfil de usuario {userId}</Link></li>
<li><Link href={`/blog/${postId}`}>Leer post: {postId}</Link></li>
<li><Link href="/docs/getting-started">Documentación</Link></li>
</ul>
</nav>
</div>
);
}
2. Usando useRouter (para navegación programática) 🧑💻
Para la navegación programática (por ejemplo, después de enviar un formulario o una acción de usuario), usa el hook useRouter.
// app/dashboard/settings/page.js (Ejemplo dentro de un Client Component)
'use client'; // Indica que es un Client Component
import { useRouter } from 'next/navigation';
export default function UserSettingsPage() {
const router = useRouter();
const handleSaveSettings = () => {
// Lógica para guardar ajustes...
console.log('Ajustes guardados!');
// Redirigir al perfil del usuario después de guardar
router.push('/dashboard');
};
return (
<div>
<h3>Ajustes Específicos del Usuario</h3>
<p>Modifica la configuración aquí.</p>
<button onClick={handleSaveSettings}>Guardar Ajustes y Volver</button>
</div>
);
}
Casos de Uso Avanzados y Buenas Prácticas 🎯
1. Metadata Dinámica
Con el App Router, la metadata (<title>, <meta description>, etc.) se puede definir en cualquier layout.js o page.js. Para rutas dinámicas, puedes generar metadata dinámica basada en los parámetros de la ruta.
// app/blog/[slug]/page.js
import { notFound } from 'next/navigation';
const posts = [
{ slug: 'mi-primer-post', title: 'Mi Primer Post en Next.js', description: 'Todo sobre mi primer post.' },
{ slug: 'tutorial-nextjs', title: 'Tutorial de Next.js Avanzado', description: 'Guía completa de Next.js.' },
];
export async function generateMetadata({ params }) {
const post = posts.find(p => p.slug === params.slug);
if (!post) {
return { title: 'Post No Encontrado' };
}
return {
title: post.title,
description: post.description,
};
}
export default function BlogPostPage({ params }) {
const post = posts.find(p => p.slug === params.slug);
if (!post) {
notFound();
}
return (
<main>
<h1>{post.title}</h1>
<p>{post.description}</p>
</main>
);
}
2. Manejo de Errores con error.js y not-found.js
El App Router facilita el manejo de errores específicos por segmento. Puedes definir archivos error.js y not-found.js en cualquier nivel de la jerarquía de rutas. error.js captura errores de renderizado en tiempo de ejecución, mientras que not-found.js se muestra cuando se llama a notFound() o una ruta no existe.
app/
├── blog/
│ ├── [slug]/
│ │ ├── error.js <-- Maneja errores para posts específicos
│ │ └── page.js
│ ├── not-found.js <-- Maneja 404 para la ruta /blog/*
│ └── page.js
├── error.js <-- Maneja errores globales
├── not-found.js <-- Maneja 404 globales
└── page.js
Ejemplo de `error.js`
```javascript 'use client'; // Componentes de Error deben ser Client Componentsexport default function Error({ error, reset }) { return (
Algo salió mal!
{error.message}
<button onClick={() => reset()}>Intentar de nuevo</details>
<details open><summary>Ejemplo de `not-found.js`</summary>
```javascript
import Link from 'next/link';
export default function NotFound() {
return (
<div>
<h2>No Encontrado</h2>
<p>La página que buscas no existe.</p>
<Link href="/">Volver al inicio</Link>
</div>
);
}
Comparativa: App Router vs. Pages Router (Rutas Dinámicas/Anidadas) 🆚
Es útil ver las diferencias clave si vienes del Pages Router.
| Característica | Pages Router (pages/) | App Router (app/) |
|---|---|---|
| Rutas Dinámicas | pages/posts/[id].js | app/posts/[id]/page.js |
| Rutas Catch-all | pages/docs/[...slug].js | app/docs/[...slug]/page.js |
| Layouts Anidados | Requiere archivos _app.js y lógica manual, _document.js para HTML. | layout.js en cada nivel de carpeta, root layout.js para HTML. |
| Acceso a parámetros | useRouter().query (Client-side) en getStaticProps/getServerSideProps (Server-side) | params prop en page.js/layout.js (Server/Client), useSearchParams() (Client) |
| Renderizado | Principalmente Client-side (CSR) con opciones SSR/SSG a nivel de página. | Predominantemente Server-side (RSC) por defecto. |
| Metadata | Con next/head dentro de componentes de página. | Con metadata objeto/función en layout.js/page.js. |
| Manejo de Errores | pages/404.js, pages/500.js | not-found.js, error.js en cualquier nivel de ruta. |
Conclusión 🎉
El sistema de rutas dinámicas y anidadas del App Router de Next.js es una herramienta extremadamente potente que te permite construir aplicaciones web complejas y bien organizadas con facilidad. Al dominar los conceptos de [param] para rutas dinámicas, [...param] para rutas catch-all y el uso estratégico de layout.js para layouts compartidos, estarás bien equipado para crear experiencias de usuario excepcionales y mantener tu código base limpio y escalable.
Recuerda practicar estos conceptos construyendo tus propias aplicaciones. Experimenta con la combinación de rutas dinámicas y anidadas, y no dudes en consultar la documentación oficial de Next.js para profundizar aún más. ¡Feliz codificación!
Tutoriales relacionados
- Optimización Avanzada de Imágenes en Next.js con next/image y Soluciones Personalizadas 📸intermediate18 min
- ¡Despliega tu App Next.js como un Pro! Guía Completa con Vercelbeginner15 min
- React Server Components en Next.js 14: Potenciando el Rendimiento y la Experiencia del Desarrolladorintermediate18 min
- ¡Next.js en el Edge! Cómo Usar Edge Functions para Apps Más Rápidas y Globales 🚀intermediate15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!