Guía completa sobre data fetching en Next.js 15 con App Router
- Static Generation (getStaticProps equivalente)
- Server-Side Rendering (getServerSideProps equivalente)
- Incremental Static Regeneration (ISR)
- generateStaticParams (getStaticPaths equivalente)
- Ejemplos prácticos con APIs reales
- Next.js 15
- TypeScript
- Tailwind CSS
- DummyJSON API
- Lista de productos con generación estática
- Rutas dinámicas con [id]
- ISR con revalidación automática
- Dashboard dinámico con autenticación
getStaticProps, getServerSideProps y getStaticPaths son funciones del Pages Router (el sistema antiguo de Next.js). En Next.js 15 con el App Router (que es la versión actual), estas funciones ya no existen como tal. El data fetching ahora se hace directamente dentro de los componentes de servido
En el Pages Router (versión antigua), estas funciones debían estar en archivos dentro de la carpeta pages/. Pero en Next.js 15 con App Router, trabajas con la carpeta app/ y el sistema es completamente diferente
Server Components (reemplazo de getStaticProps y getServerSideProps) En Next.js 15, puedes hacer fetch directamente en los componentes de servidor:
// app/pokemon/page.tsx
export default async function PokemonPage() {
// Fetch directo - se ejecuta en el servidor
const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=20');
const data = await res.json();
return (
<div>
<h1>Pokémon List</h1>
<ul>
{data.results.map((pokemon: any) => (
<li key={pokemon.name}>{pokemon.name}</li>
))}
</ul>
</div>
);
}
Este componente es estático por defecto (similar a getStaticProps)
Para controlar el comportamiento (estático vs dinámico):
// Comportamiento estático (como getStaticProps)
const res = await fetch('https://api.com/data', {
cache: 'force-cache' // cachea indefinidamente
});
// Comportamiento dinámico (como getServerSideProps)
const res = await fetch('https://api.com/data', {
cache: 'no-store' // sin cache, siempre fresh
});
ISR permite regenerar páginas estáticas después del build sin reconstruir todo el sitio. Es ideal para contenido que cambia poco pero necesita actualizarse ocasionalmente:
// app/characters/[id]/page.tsx
const CharacterPage = async ({ params }: { params: { id: string } }) => {
const res = await fetch(
`https://rickandmortyapi.com/api/character/${params.id}`,
{
next: { revalidate: 60 } // se regenera cada 60s
}
);
const characater = await res.json();
return (
<div>
<h1>{characater.name}</h1>
<p>Status: {characater.status}</p>
</div>
)
}
export default CharacterPage
Fetch con cache: 'force-cache' (Estático)
-
Contenido que no cambia o cambia muy raramente
-
Blogs, documentación, páginas de marketing
-
Ventajas: SEO excelente, velocidad máxima
-
Ejemplo: Lista de categorías fijas
Fetch con cache: 'no-store' (Dinámico)
-
Contenido específico del usuario (dashboards, perfiles)
-
Datos que cambian constantemente
-
Ventajas: Siempre actualizado
-
Desventajas: Más lento, más carga en el servidor
-
Ejemplo: Carrito de compras, notificaciones
ISR con revalidate (Regeneración Incremental)
-
Contenido que se actualiza periódicamente
-
Catálogos de productos, feeds de noticias
-
Ventajas: Balance entre velocidad y frescura
-
Ejemplo: Lista de Pokémon que se actualiza cada hora
// app/characters/page.tsx - Lista con ISR
export default async function CharactersPage() {
const res = await fetch(
'https://rickandmortyapi.com/api/character',
{ next: { revalidate: 3600 } } // Regenera cada hora
);
const data = await res.json();
return (
<div>
<h1>Rick and Morty Characters</h1>
<div>
{data.results.map((char: any) => (
<div key={char.id}>
<h2>{char.name}</h2>
<p>{char.species}</p>
</div>
))}
</div>
</div>
);
}
Cuando uses una base de datos (Prisma, Drizzle, etc.), el mismo principio aplica:
const ProductsPage = () => {
// consulta directa en el server components
const products = await db.product.findMany({
where: { active: true }
})
return (
<div>
{products.map(product => {
return (
<div key={product.id}>{product.name}</div>
)
})}
</div>
)
}
export default ProductsPage
Puedes controlar el caching usando unstable_cache de Next.js o las opciones de fetch para APIs externas
Para datos en el cliente (interactividad, datos específicos del usuario después del render inicial), usa 'use client' con SWR o React Query:
'use client';
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(r => r.json());
export default function ClientPokemon() {
const { data, error, isLoading } = useSWR(
'https://pokeapi.co/api/v2/pokemon',
fetcher
);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return <div>{/* render data */}</div>;
}
Este enfoque es útil para datos que necesitan actualizarse sin recargar la página.
En Next.js 15 ya no usas getStaticProps, getServerSideProps ni getStaticPaths directamente. En su lugar, haces fetch dentro de Server Components y controlas el comportamiento con las opciones de cache. ISR se logra con la opción revalidate en el fetch.