App Router y Enrutamiento Basado en Archivos
Domina el App Router, layouts, páginas y el sistema de enrutamiento basado en archivos
El App Router
Next.js 13+ introdujo el App Router, un nuevo paradigma para construir aplicaciones React con
layouts anidados, React Server Components y streaming. El App Router usa el directorio app/
y es el enfoque recomendado para proyectos nuevos.
📁 Archivos Especiales
page.tsx - UI única de la páginalayout.tsx - Layout compartidoloading.tsx - UI de cargaerror.tsx - Límite de errornot-found.tsx - Página 404template.tsx - Layout re-renderizadoroute.ts - Endpoint APIdefault.tsx - Fallback de ruta paralelaEnrutamiento Básico
Las rutas se derivan de la estructura de carpetas. Las carpetas estáticas mapean a segmentos de URL, y los corchetes crean segmentos dinámicos o catch-all.
// Enrutamiento basado en archivos - las carpetas se convierten en segmentos de URL
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/
│ └── page.tsx → /blog/:slug
├── products/
│ ├── page.tsx → /products
│ └── [...categories]/
│ └── page.tsx → /products/* (catch-all)
└── (marketing)/ → Grupo de ruta (sin impacto en URL)
├── about/
└── contact/
Layouts
Los layouts envuelven páginas y persisten entre navegaciones. Se requiere un layout raíz, y los layouts anidados te permiten compartir UI dentro de una sección.
// app/layout.tsx - Layout raíz (requerido)
import './globals.css';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="es">
<body>
<nav>{/* Navegación global */}</nav>
<main>{children}</main>
<footer>{/* Pie de página global */}</footer>
</body>
</html>
);
}
// app/dashboard/layout.tsx - Layout anidado
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex">
<aside className="w-64">
{/* Barra lateral del dashboard */}
</aside>
<div className="flex-1">{children}</div>
</div>
);
}
// Los layouts persisten entre navegaciones
// El estado se conserva al navegar entre páginas
// Solo el contenido de la página se re-renderiza
Rutas Dinámicas
Los segmentos dinámicos leen parámetros de la URL, y generateStaticParams puede pre-construir
rutas conocidas en tiempo de build.
// app/blog/[slug]/page.tsx - Segmento dinámico
interface PageProps {
params: Promise<{ slug: string }>;
}
export default async function BlogPost({ params }: PageProps) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}
// Generar rutas estáticas en tiempo de build
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
// app/products/[...categories]/page.tsx - Catch-all
// Coincide con: /products/a, /products/a/b, /products/a/b/c
interface PageProps {
params: Promise<{ categories: string[] }>;
}
export default async function Products({ params }: PageProps) {
const { categories } = await params;
// categories = ['a', 'b', 'c'] para /products/a/b/c
return <div>Categorías: {categories.join(' > ')}</div>;
}
// app/[[...slug]]/page.tsx - Catch-all opcional
// También coincide con la raíz: /
Estados de Carga y Error
Agrega UI de carga a nivel de ruta y límites de error para mejorar la UX durante el renderizado async y para recuperarse de errores en tiempo de ejecución.
// app/dashboard/loading.tsx - UI de carga
export default function Loading() {
return (
<div className="flex items-center justify-center h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500" />
</div>
);
}
// app/dashboard/error.tsx - Límite de error
'use client'; // Los límites de error deben ser componentes cliente
import { useEffect } from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<div className="p-8 text-center">
<h2 className="text-2xl font-bold text-red-600">¡Algo salió mal!</h2>
<button
onClick={reset}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
>
Intentar de nuevo
</button>
</div>
);
}
// app/not-found.tsx - 404 personalizado
import Link from 'next/link';
export default function NotFound() {
return (
<div className="text-center py-20">
<h2 className="text-4xl font-bold">404</h2>
<p>Página no encontrada</p>
<Link href="/" className="text-blue-500">Ir al inicio</Link>
</div>
);
}
Grupos de Rutas
Los grupos de rutas organizan carpetas sin cambiar URLs, facilitando la separación de secciones de la app con diferentes layouts.
// Agrupar rutas sin afectar la estructura de URL
// Usar paréntesis: (nombreGrupo)
app/
├── (marketing)/ # Sin impacto en URL
│ ├── about/
│ │ └── page.tsx → /about
│ ├── blog/
│ │ └── page.tsx → /blog
│ └── layout.tsx # Layout compartido de marketing
├── (shop)/
│ ├── products/
│ │ └── page.tsx → /products
│ ├── cart/
│ │ └── page.tsx → /cart
│ └── layout.tsx # Layout compartido de tienda
└── layout.tsx # Layout raíz
// ¡Cada grupo puede tener su propio layout!
// Útil para diferentes secciones con diferentes diseños
Navegación
Usa Link para navegación del lado del cliente y el router para redirecciones programáticas o actualizaciones.
// Usar el componente Link para navegación del lado del cliente
import Link from 'next/link';
export default function Nav() {
return (
<nav>
<Link href="/">Inicio</Link>
<Link href="/about">Acerca de</Link>
<Link href="/blog/hello-world">Post del Blog</Link>
{/* Prefetch es automático para links en viewport */}
<Link href="/dashboard" prefetch={false}>
Dashboard
</Link>
{/* Reemplazar historial en lugar de push */}
<Link href="/login" replace>Iniciar Sesión</Link>
</nav>
);
}
// Navegación programática
'use client';
import { useRouter } from 'next/navigation';
export default function Form() {
const router = useRouter();
const handleSubmit = async () => {
await saveData();
router.push('/success'); // Navegar
router.replace('/login'); // Reemplazar
router.back(); // Volver
router.forward(); // Adelante
router.refresh(); // Actualizar componentes servidor
};
}
Rutas Paralelas
Las rutas paralelas renderizan múltiples segmentos en el mismo layout, útil para dashboards, tabs y layouts complejos con áreas independientes.
// Mostrar múltiples páginas simultáneamente en el mismo layout
// Usar convención @carpeta
app/
├── layout.tsx
├── page.tsx
├── @dashboard/
│ └── page.tsx
└── @analytics/
└── page.tsx
// app/layout.tsx
export default function Layout({
children,
dashboard,
analytics,
}: {
children: React.ReactNode;
dashboard: React.ReactNode;
analytics: React.ReactNode;
}) {
return (
<div>
{children}
<div className="grid grid-cols-2 gap-4">
{dashboard}
{analytics}
</div>
</div>
);
}
// Ideal para dashboards, modales y renderizado condicional
✅ Mejores Prácticas de Enrutamiento
- • Usa layouts para compartir UI entre rutas
- • Agrega loading.tsx para mejor UX durante navegación
- • Implementa límites error.tsx en puntos estratégicos
- • Usa grupos de rutas para organizar sin afectar URLs
- • Genera parámetros estáticos para rutas dinámicas conocidas
- • Usa el componente Link para toda navegación