Pages, Layouts y Navegacion

Pages

Un page.tsx es la UI unica de una ruta. Es un Server Component por defecto:

tsx
// app/productos/page.tsx
export default function ProductosPage() {
  return (
    <div>
      <h1>Nuestros Productos</h1>
      {/* Este componente se renderiza en el servidor */}
    </div>
  )
}

Layouts

Un layout.tsx envuelve a todas las paginas hijas y persiste entre navegaciones (no se re-renderiza cuando cambias de pagina):

tsx
// app/layout.tsx — Layout raiz
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="es">
      <body>
        <header>Mi App</header>
        <main>{children}</main>
        <footer>2026</footer>
      </body>
    </html>
  )
}

Layouts anidados

Puedes tener layouts dentro de layouts. Cada uno agrega UI sin reemplazar al padre:

tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="flex">
      <aside className="w-64 border-r p-4">
        <nav>
          <a href="/dashboard">Inicio</a>
          <a href="/dashboard/ventas">Ventas</a>
          <a href="/dashboard/clientes">Clientes</a>
        </nav>
      </aside>
      <div className="flex-1 p-6">{children}</div>
    </div>
  )
}

Cuando navegas entre /dashboard/ventas y /dashboard/clientes, el sidebar no se re-renderiza. Solo cambia el contenido.

Ejemplo: layout con sidebar activa

tsx
// app/dashboard/layout.tsx
import Link from "next/link"

const navItems = [
  { href: "/dashboard", label: "Inicio" },
  { href: "/dashboard/ventas", label: "Ventas" },
  { href: "/dashboard/clientes", label: "Clientes" },
]

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="flex min-h-screen">
      <aside className="w-64 bg-gray-900 p-4">
        <h2 className="text-lg font-bold mb-4">Dashboard</h2>
        <nav className="space-y-1">
          {navItems.map((item) => (
            <Link
              key={item.href}
              href={item.href}
              className="block px-3 py-2 rounded hover:bg-gray-800"
            >
              {item.label}
            </Link>
          ))}
        </nav>
      </aside>
      <main className="flex-1 p-8">{children}</main>
    </div>
  )
}

Para navegacion del lado del cliente (sin recargar la pagina):

tsx
import Link from "next/link"

export default function Nav() {
  return (
    <nav>
      <Link href="/">Inicio</Link>
      <Link href="/blog">Blog</Link>
      <Link href="/productos/camiseta-1">Producto</Link>
    </nav>
  )
}

Link hace prefetch automatico de las rutas visibles en el viewport. Cuando el usuario da click, la navegacion es instantanea.

useRouter hook

Para navegacion programatica (en Client Components):

tsx
"use client"
import { useRouter } from "next/navigation"

export default function LoginForm() {
  const router = useRouter()

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    const success = await login()

    if (success) {
      router.push("/dashboard")
    }
  }

  return <form onSubmit={handleSubmit}>{/* ... */}</form>
}

Metodos disponibles:

  • router.push("/ruta") — Navegar a una ruta
  • router.replace("/ruta") — Navegar sin agregar al historial
  • router.back() — Ir atras
  • router.refresh() — Refrescar la ruta actual (re-fetch de datos)

redirect()

Para redirecciones en Server Components o Server Actions:

tsx
import { redirect } from "next/navigation"

export default async function AdminPage() {
  const user = await getUser()

  if (!user.isAdmin) {
    redirect("/login")
  }

  return <div>Panel de admin</div>
}
redirect vs router.push

Usa redirect() en Server Components y Server Actions. Usa router.push() en Client Components con event handlers.