nextjs-app-router-routing

Navegación en Next.JS

  • 5 min

En la arquitectura de Single Page Applications (SPA) con Vite, la definición de las rutas era una tarea explícita. Teníamos un archivo central (normalmente App.jsx o main.jsx) donde importábamos componentes y los asignábamos a URLs mediante <Route path="/about" element={<About />} />.

En Next.js, esa capa de configuración cambia por una convención de carpetas.

Next.js adopta el paradigma de convención sobre configuración. No escribimos un archivo de rutas; la estructura de nuestras carpetas define las rutas.

Si creas una carpeta, creas una ruta. Si borras la carpeta, borras la ruta. Es así de directo y, una vez te acostumbras, es difícil volver atrás.

Definición de rutas estáticas

El App Router funciona mediante una regla de oro:

Una carpeta en src/app define un segmento de ruta. Pero la ruta solo es accesible públicamente si dicha carpeta contiene un archivo especial llamado page.tsx.

Veamos cómo se traduce la estructura de archivos a URLs en el navegador:

  • src/app/page.tsxmiapp.com/ (Home)
  • src/app/contacto/page.tsxmiapp.com/contacto
  • src/app/blog/page.tsxmiapp.com/blog
  • src/app/blog/primer-post/page.tsxmiapp.com/blog/primer-post

Si creáis una carpeta src/app/componentes, pero dentro no ponéis un page.tsx, esa ruta no existirá. Esto nos permite tener carpetas para organizar código (componentes, utilidades, estilos) junto a las páginas sin miedo a que se expongan como URLs accidentalmente.

Layouts y anidamiento

Aquí es donde Next.js simplifica bastante la arquitectura. Pensad en una sección de “Dashboard” (/dashboard) que tiene una barra lateral (Sidebar) propia, distinta a la de la Home. Y dentro del Dashboard, tenéis /dashboard/ajustes y /dashboard/perfil.

En React Router, gestionar que el Sidebar no se desmontara al navegar entre ajustes y perfil era complejo. En Next.js es nativo gracias a los Nested Layouts.

Podemos crear un archivo layout.tsx en cualquier nivel de la jerarquía de carpetas.

src/app/
├── layout.tsx      (Layout Raíz: html, body, Navbar Global)
├── page.tsx        (Home)
└── dashboard/
    ├── layout.tsx  (Layout del Dashboard: Sidebar)
    ├── page.tsx    (Vista principal del Dashboard)
    └── ajustes/
        └── page.tsx (Vista de Ajustes)

Copied!

Cuando entráis en /dashboard/ajustes:

Next.js renderiza el Root Layout.

Dentro, renderiza el Dashboard Layout.

Dentro, renderiza la Page de Ajustes.

// src/app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="flex">
      <aside className="w-64 bg-gray-100 p-4">
        {/* Este Sidebar es persistente */}
        <nav>...</nav>
      </aside>
      
      <section className="flex-1 p-8">
        {/* Aquí se inyectan las páginas hijas (ajustes, perfil...) */}
        {children}
      </section>
    </div>
  );
}

Copied!

Al navegar entre páginas que comparten el mismo Layout, el Layout no se re-renderiza. Se mantiene el estado (scroll del sidebar, texto en inputs de búsqueda, etc.).

Rutas dinámicas

¿Qué pasa si queremos una ruta para ver el detalle de un producto, donde el ID cambia? (/productos/1, /productos/zapato-nike). No podemos crear carpetas infinitas.

Para esto usamos los corchetes [] en el nombre de la carpeta.

Por ejemplo

src/app/productos/[id]/page.tsx
Copied!

El nombre que pongamos dentro de los corchetes (en este caso id) será el nombre del parámetro que recibiremos en nuestro componente.

// src/app/productos/[id]/page.tsx

// Definimos el tipo para las props
interface Props {
  params: Promise<{
    id: string;
  }>;
}

export default async function ProductoDetalle({ params }: Props) {
  const { id } = await params;

  return (
    <div>
      <h1>Viendo el producto con ID: {id}</h1>
      {/* Aquí haríamos el fetch a la base de datos usando este ID */}
    </div>
  );
}

Copied!

Así, cualquier URL que coincida con el patrón /productos/lo-que-sea, renderizará este componente.

Finalmente, necesitamos movernos entre estas rutas. Recordamos que no podemos usar la etiqueta HTML estándar <a href="/about">.

Una etiqueta <a> provoca una recarga completa de la página (Hard Refresh), perdiendo el estado de React y descargando todo el JS de nuevo.

Next.js nos proporciona el componente Link, que extiende la etiqueta <a> pero realiza la navegación en el cliente (Client-Side Navigation).

import Link from 'next/link';

export default function Navbar() {
  return (
    <nav className="flex gap-4">
      <Link href="/">Home</Link>
      
      <Link href="/dashboard" className="text-blue-500">
        Ir al Dashboard
      </Link>
      
      <Link href="/productos/123">
        Ver Producto 123
      </Link>
    </nav>
  );
}

Copied!