Hasta Next.js 15, el cacheo de fetch funcionaba por heurísticas implícitas: por defecto se cacheaba, salvo que algo (una cookie, un fetch con no-store, ciertas APIs dinámicas) marcara la ruta entera como dinámica. Funcionaba, pero era difícil predecir qué se cacheaba con solo mirar el código. Cache Components en Next.js 16 invierte el modelo: nada se cachea a menos que lo marques explícitamente con la directiva "use cache".
// Next.js 15: ¿esto se cachea? Depende de qué haga el resto de la ruta
async function Stats() {
const res = await fetch('https://api.example.com/stats');
return <p>{(await res.json()).visits} visitas</p>;
}
Si en algún otro punto de la misma ruta usabas cookies() o headers(), toda la ruta pasaba a ser dinámica y este fetch dejaba de cachearse, sin que hubiera ninguna señal visual en este componente de que eso estaba pasando.
Marcas con "use cache" la función, componente o archivo que quieres que sea cacheable. Todo lo demás es dinámico por defecto.
// Next.js 16: explícito
async function Stats() {
'use cache';
const res = await fetch('https://api.example.com/stats');
return <p>{(await res.json()).visits} visitas</p>;
}
Ese Stats se cachea independientemente de lo que haga el resto de la página. Puedes tener una ruta con partes estáticas cacheadas y partes dinámicas (que leen cookies, por ejemplo) conviviendo, sin que una arrastre a la otra.
Dos funciones nuevas para gestionar la caché desde dentro del componente:
import { cacheTag, cacheLife } from 'next/cache';
async function ProductPrice({ id }: { id: string }) {
'use cache';
cacheTag(`product-${id}`);
cacheLife('hours');
const res = await fetch(`https://api.example.com/products/${id}`);
return <span>{(await res.json()).price} €</span>;
}
cacheTag asocia una etiqueta a esa entrada de caché, que luego puedes invalidar puntualmente con revalidateTag('product-123') desde una Server Action o Route Handler, sin tocar el resto de la caché.cacheLife define durante cuánto tiempo es válida esa entrada ('seconds', 'minutes', 'hours', 'days', o un objeto custom con stale, revalidate y expire).Si toda una ruta o layout es esencialmente estático, puedes poner la directiva al principio del archivo en vez de en cada función:
// app/blog/page.tsx
'use cache';
export default async function BlogPage() {
const posts = await fetchBlog();
return <PostList posts={posts} />;
}
Cache Components es la pieza que hace útil a Partial Prerendering (PPR): Next.js pre-renderiza en build el "cascarón" estático de la página —todo lo marcado con "use cache" o sin dependencias dinámicas— y deja huecos que se rellenan en el servidor en cada request para las partes dinámicas (las que leen cookies, headers, o searchParams). El resultado es un TTFB casi instantáneo para la parte estática, con la parte personalizada llegando por streaming.
| Next.js 15 (heurístico) | Next.js 16 (Cache Components) | |
|---|---|---|
| Qué se cachea por defecto | Depende de la ruta completa | Nada, salvo que lo marques |
| Cómo se marca | Implícito | "use cache" explícito |
| Invalidación selectiva | revalidatePath a nivel ruta | cacheTag + revalidateTag granular |
| Predictibilidad leyendo el código | Baja | Alta |
El cambio de mentalidad es el mismo que en Server Components: pasar de "el framework decide por ti" a "tú declaras la intención, el framework la respeta". Cuesta un poco de disciplina extra marcar explícitamente cada pieza cacheable, pero a cambio ganas algo que antes no tenías: poder mirar un componente y saber, sin adivinar, si se está cacheando.
React 19.2 trae el componente <ViewTransition> y Next.js 16 lo conecta con las navegaciones del App Router: transiciones animadas entre páginas sin librerías externas ni CSS manual.
Los Server Components cambian dónde y cómo se cargan los datos en una app React: sin useEffect, sin estados de carga manuales, ejecutando fetch directamente en el servidor durante el render.