Pages, Layouts y Navegacion
Pages
Un page.tsx es la UI unica de una ruta. Es un Server Component por defecto:
// 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):
// 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:
// 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
// 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>
)
}
Navegacion
Link component
Para navegacion del lado del cliente (sin recargar la pagina):
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):
"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 rutarouter.replace("/ruta")— Navegar sin agregar al historialrouter.back()— Ir atrasrouter.refresh()— Refrescar la ruta actual (re-fetch de datos)
redirect()
Para redirecciones en Server Components o Server Actions:
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.