TechLead

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ágina
layout.tsx - Layout compartido
loading.tsx - UI de carga
error.tsx - Límite de error
not-found.tsx - Página 404
template.tsx - Layout re-renderizado
route.ts - Endpoint API
default.tsx - Fallback de ruta paralela

Enrutamiento 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

📖 Documentación de Enrutamiento de Next.js →

✅ 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