En las aplicaciones web clásicas, la navegación a través de enlaces resultaba en la destrucción completa de la página actual: el navegador solicitaba nuevamente el HTML, parseaba el CSS, descargaba los scripts y reiniciaba el estado de todos los formularios, el scroll y los menús abiertos.
El Next.js 16 App Router resuelve este problema fundamental mediante dos mecanismos interconectados:
layout.tsx): Envoltorios de interfaz comunes (Header, Sidebar, Navigation) que no se vuelven a renderizar y conservan su estado (Preserved State) al navegar entre páginas secundarias (Subtree Reconciliation). Al hacer clic en un enlace, solo se vuelve a dibujar el componente hoja page.tsx.<Link> optimizado: Un componente de navegación que implementa "navegación suave" (Soft Navigation) sin recargar la pestaña. El componente realiza automáticamente la precarga (Prefetching) del código y los datos de la ruta de destino en segundo plano, tan pronto como el enlace entra en el área visible de la pantalla (Viewport).Veamos cómo funciona el renderizado al navegar entre páginas:
+-------------------------------------------------------------------------+
| CONSERVACIÓN DEL ESTADO EN DISEÑOS ANIDADOS |
+-------------------------------------------------------------------------+
| [RootLayout: app/layout.tsx] -----------------------------------------+ |
| | Encabezado del sitio, Carrito, Tema (NUNCA SE VUELVEN A RENDERIZAR) | |
| | | |
| | [CatalogLayout: app/catalog/layout.tsx] --------------------------+ | |
| | | Filtros laterales, Barra lateral (¡CONSERVAN SU ESTADO INTERNO!) | | |
| | | | | |
| | | Al navegar de /catalog/phones a /catalog/laptops: | | |
| | | SOLO CAMBIA EL COMPONENTE HOJA: | | |
| | | Era: [Page: PhonesPage] | | |
| | | Ahora: [Page: LaptopsPage] | | |
| | +-----------------------------------------------------------------+ | |
| +---------------------------------------------------------------------+ |
+-------------------------------------------------------------------------+template.tsx
El IntersectionObserver del navegador detecta la aparición del componente `<Link>` en la pantalla.
Next.js descarga silenciosamente la carga útil (Flight payload) del servidor de la ruta de destino en la caché del enrutador (Router Cache).
El manejador `onClick` intercepta el evento (`e.preventDefault()`) y accede a la caché ya preparada.
El `layout.tsx` padre conserva su DOM y estado, mientras que el nuevo `page.tsx` se monta instantáneamente.
El usuario ha introducido texto en un input de búsqueda, ubicado dentro de `app/catalog/layout.tsx`, y ha hecho clic en un enlace que lleva a otra categoría del catálogo. ¿Qué sucederá con el texto introducido en el input?
app/layout.tsx raíz con la conexión de fuentes (next/font), estilos globales y las etiquetas <html> y <body>.template.tsx en lugar de layout.tsx cuando se requiere reiniciar las animaciones CSS en cada transición.app/layout.tsx#El diseño raíz es obligatorio para cualquier proyecto de Next.js. Debe contener las etiquetas <html> y <body>:
import type { Metadata } from 'next';
import Link from 'next/link';
import '@/app/globals.css';
export const metadata: Metadata = {
title: 'WPSKILLS Platform',
description: 'Plataforma de aprendizaje de ingeniería de nueva generación',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="es" className="dark">
<body className="bg-bg-base text-text-primary min-h-screen flex flex-col antialiased">
{/* Encabezado global (se conserva en todas las páginas) */}
<header className="border-b border-border p-4 bg-bg-surface/80 backdrop-blur-md sticky top-0 z-50">
<nav className="max-w-6xl mx-auto flex items-center justify-between">
<Link href="/" className="font-bold text-accent-cyan text-lg">
WPSKILLS
</Link>
<div className="flex gap-4 text-sm font-medium">
<Link href="/catalog" className="hover:text-accent-cyan transition-colors">
Catálogo
</Link>
<Link href="/dashboard" className="hover:text-accent-cyan transition-colors">
Panel de control
</Link>
</div>
</nav>
</header>
{/* Aquí se insertan las páginas secundarias y los sub-layouts */}
<main className="flex-1">{children}</main>
<footer className="border-t border-border p-6 text-center text-xs text-text-muted">
© 2026 WPSKILLS. Todos los derechos reservados.
</footer>
</body>
</html>
);
}<Link>#El componente <Link> de next/link reemplaza la etiqueta HTML estándar <a>:
import Link from 'next/link';
export function NavigationCards() {
return (
<div className="grid grid-cols-2 gap-4">
{/* Navegación suave con precarga automática por defecto */}
<Link
href="/catalog/typescript"
className="p-6 rounded-xl border border-border bg-bg-surface hover:border-accent-cyan transition-all"
>
<h3 className="font-bold">Dominio de TypeScript →</h3>
<p className="text-sm text-text-muted mt-1">Domina la tipificación estricta</p>
</Link>
{/* Desactivar precarga para páginas poco visitadas o pesadas */}
<Link
href="/admin/heavy-reports"
prefetch={false}
className="p-6 rounded-xl border border-border bg-bg-surface hover:border-accent-violet transition-all"
>
<h3 className="font-bold">Informes (Sin Precarga) →</h3>
</Link>
</div>
);
}layout.tsx y template.tsx#Si necesitas que un componente se vuelva a montar desde cero en cada navegación (por ejemplo, para iniciar una animación de entrada o restablecer el estado interno de un formulario), utiliza el archivo template.tsx:
'use client';
import React from 'react';
// ¡template.tsx crea una NUEVA instancia del nodo DOM en cada navegación!
export default function CatalogTemplate({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="animate-in fade-in duration-300">
{children}
</div>
);
}¿En qué caso un desarrollador debe elegir `template.tsx` en lugar de `layout.tsx`?
Las etiquetas <html> y <body> están permitidas estrictamente en una única instancia — en el app/layout.tsx raíz. Intentar añadirlas en un app/catalog/layout.tsx anidado provocará un conflicto sintáctico del DOM y un error de hidratación fatal.
A menudo, los principiantes escriben <div onClick={() => router.push('/path')}>.
Por qué esto es una mala práctica:
href real en el DOM (impacto negativo en el SEO);<Link>!