Next.js App Router: lo que nadie te explica
Cuando migré este blog a App Router (hoy corre en Next.js 16.4 con React 19) no venía de cero en React: venía de Pages Router, que es bastante más predecible. App Router cambia el modelo mental de cómo piensas el rendering y el fetching de datos, y la documentación oficial te da los conceptos pero no te dice qué vas a romper cuando los combines.
El cambio de modelo mental
En Pages Router, cada archivo en pages/ es una página. Haces fetch de datos en getServerSideProps o getStaticProps y los recibes como props en el componente. Claro, explícito.
En App Router, todo componente es Server Component por defecto. Eso significa que se renderiza en el servidor, puede hacer fetches directamente (sin API routes de por medio), y su HTML llega al cliente ya renderizado. Para que un componente corra en el cliente (para usar useState, useEffect, eventos), necesitas declarar "use client" al principio del archivo.
Esto no es solo sintaxis diferente — es una arquitectura diferente.
Lo que nadie te explica sobre Server Components
No puedes pasar funciones como props de un Server Component a un Client Component. Las funciones no son serializables — no pueden cruzar el límite servidor/cliente. Si necesitas pasar un handler, ese componente tiene que ser Client Component.
Puedes importar un Client Component dentro de un Server Component, pero no al revés. Un Client Component no puede importar ni renderizar un Server Component directamente. Puedes pasarlo como prop (el patrón de children), pero no importarlo.
Los Server Components no re-renderizan. No tienen estado, no tienen ciclo de vida en el cliente. Si necesitas reactividad, ese componente es Client Component.
params y searchParams son asíncronos. Desde Next.js 15 llegan como Promises: en una página o layout tienes que hacer const { slug } = await params. Si vienes de tutoriales viejos que los leen directamente, es el primer error que te vas a encontrar al actualizar.
El caching que te va a confundir
App Router tiene varias capas de cache que interactúan entre sí: el Router Cache (en memoria, del lado del cliente), el Full Route Cache (en el servidor, entre builds), y el Data Cache (para las respuestas de fetch).
Aquí es donde más tutoriales se han quedado viejos. Hasta Next.js 14, fetch se cacheaba por defecto. Desde la 15 ya no: cada fetch se vuelve a pedir salvo que digas lo contrario. Y en Next.js 16 el modelo recomendado es explícito: activas Cache Components y marcas con la directiva "use cache" lo que quieres cachear.
// next.config.ts
const nextConfig = { cacheComponents: true }
export default nextConfig
import { cacheLife } from 'next/cache'
// Sin "use cache": se pide en cada request
const fresh = await fetch('https://api.ejemplo.com/datos')
// Cacheado, y revalidado como mucho cada hora
async function getDatos() {
'use cache'
cacheLife('hours')
const res = await fetch('https://api.ejemplo.com/datos')
return res.json()
}
// La opción clásica sigue funcionando: revalidar cada 60 segundos
const data = await fetch('https://api.ejemplo.com/datos', {
next: { revalidate: 60 }
})
Si tu página muestra datos que parecen desactualizados, busca qué está marcado con "use cache" o con revalidate. Si pasa lo contrario (actualizaste desde la 14 y de repente todo va más lento), es porque fetch dejó de cachearse solo.
Layouts anidados: el poder y la trampa
Los layouts en App Router son persistentes entre navegaciones. Si tienes un layout que tiene estado (por ejemplo, un sidebar con un ítem seleccionado), ese estado se mantiene mientras navegas entre rutas que comparten el mismo layout.
Eso es poderoso. También significa que si pones lógica de inicialización en el layout, esa lógica no corre de nuevo en cada navegación — solo cuando el layout se monta por primera vez.
Lo que sí cambió para mejor
Fetch de datos más simple. En lugar de APIs, props y contexto para mover datos del servidor al cliente, simplemente haces async/await en el componente que los necesita. Menos indirección.
Layouts sin re-renderizar. La barra de navegación no parpadea en cada cambio de página porque el layout no se desmonta y remonta — persiste.
Streaming por defecto. Con Suspense, puedes mostrar partes de la página mientras otras todavía cargan, sin trabajo extra.
La recomendación
Si estás empezando un proyecto nuevo con Next.js, usa App Router desde el principio. No es más difícil — es diferente. Una vez que interiorizas el modelo de Server vs Client Components, el código es más limpio.
Si estás migrando desde Pages Router, hazlo de forma gradual. Next.js soporta ambos routers en el mismo proyecto durante la migración. Migra ruta por ruta, no todo de golpe.
Si usas TypeScript (y deberías), App Router tiene soporte nativo con tipos para params, searchParams y Server Actions. Y si te interesa cómo construí este mismo blog con Next.js y Obsidian, tengo un post dedicado.