~/blog/nextjs-app-router-lo-que-nadie-te-explica

El cambio de modelo mental

Cuando migré a App Router de Next.js 15 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 pensás 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. Hacés fetch de datos en getServerSideProps o getStaticProps y los recibís 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), necesitás 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 podés 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 necesitás pasar un handler, ese componente tiene que ser Client Component.

Podés 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. Podés 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 necesitás reactividad, ese componente es Client Component.

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).

El comportamiento por defecto cachea agresivamente. Si hacés un fetch en un Server Component, esa respuesta se cachea y no se vuelve a pedir a menos que la invalides explícitamente o uses cache: 'no-store'.

// Se cachea indefinidamente (malo para datos dinámicos)
const data = await fetch('https://api.ejemplo.com/datos')

// No se cachea
const data = await fetch('https://api.ejemplo.com/datos', {
  cache: 'no-store'
})

// Se revalida cada 60 segundos
const data = await fetch('https://api.ejemplo.com/datos', {
  next: { revalidate: 60 }
})

Si tu página muestra datos que parecen desactualizados, el problema es casi siempre el cache.

Layouts anidados: el poder y la trampa

Los layouts en App Router son persistentes entre navegaciones. Si tenés un layout que tiene estado (por ejemplo, un sidebar con un ítem seleccionado), ese estado se mantiene mientras navegás entre rutas que comparten el mismo layout.

Eso es poderoso. También significa que si ponés 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 hacés async/await en el componente que los necesita. Menos indirección.

Layouts sin re-renderizar. La barra de navegación no parpade en cada page transition porque el layout no se desmonta y remonta — persiste.

Streaming por defecto. Con Suspense, podés 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, usá App Router desde el principio. No es más difícil — es diferente. Una vez que internalizás el modelo de Server vs Client Components, el código es más limpio.

Si estás migrando desde Pages Router, hacelo gradualmente. Next.js soporta ambos routers en el mismo proyecto durante la migración. Migrá ruta por ruta, no todo de golpe.