- La dirección futura de Next.js resulta interesante
- Hubo algunos problemas con Server Actions, pero se ve potencial de mejora con
useOptimistic y useFormStatus de React 19
- El enfoque de
useFetcher de Remix también ofrece una buena DX
- El PPR (Partial Pre-rendering) de Next.js y el nuevo sistema de caché granular destacan especialmente
- En general, deja una impresión muy positiva
The Big Picture
- El nuevo sistema de caché se puede activar de forma experimental en
next.config.js
- Se pueden definir perfiles de caché para configurar distintos tiempos de expiración y ciclos de revalidación
// next.config.js
const config = {
experimental: {
// Activa el nuevo sistema de caché. Ahora puedes usar `use cache` en el código
dynamicIO: true,
// Opcional: configuración de perfiles de caché
cacheLife: {
blog: {
stale: 3600, // Mantener caché del cliente: 1 hora
revalidate: 900, // Refrescar en el servidor: 15 minutos
expire: 86400, // Vida máxima: 1 día
},
},
},
};
Uso básico de use cache
- Se puede habilitar caché a nivel de archivo, componente o función mediante la declaración
"use cache"
- En los ejemplos de código, basta con agregar
use cache para aplicar caché fácilmente
- Con
cacheTag, revalidateTag, etc., se puede invalidar la caché en el momento deseado
// 1. Caché a nivel de archivo
"use cache";
export default function Page() {
return <div>Cached Page</div>;
}
// 2. Caché a nivel de componente
export async function PriceDisplay() {
"use cache";
const price = await fetchPrice();
return <div>${price}</div>;
}
// 3. Caché a nivel de función
export async function getData() {
"use cache";
return await db.query();
}
Caché basada en etiquetas
import { unstable_cacheTag as cacheTag, revalidateTag } from 'next/cache';
// Guardar en caché un grupo específico de datos
export async function ProductList() {
'use cache';
cacheTag('products');
const products = await fetchProducts();
return <div>{products}</div>;
}
// Invalidar la caché cuando cambien los datos
export async function addProduct() {
'use server';
await db.products.add(...);
revalidateTag('products');
}
Perfil de Cache personalizado
- Con
unstable_cacheLife se pueden cargar los perfiles de caché definidos en next.config.js
- La política de caché se aplica usando el nombre del perfil declarado dentro del código (por ejemplo,
"blog")
import { unstable_cacheLife as cacheLife } from "next/cache";
export async function BlogPosts() {
"use cache";
cacheLife("blog"); // Usa el perfil de caché predefinido para blog
return await fetchPosts();
}
Puntos importantes que pueden pasar desapercibidos
Generación automática de claves de caché
- Los
props y los arguments del componente se incluyen automáticamente en la clave de caché
- Los valores no serializables (como funciones) se manejan como "referencias no modificables"
export async function UserCard({ id, onDelete }) {
"use cache";
// id se incluye en la clave de caché
// onDelete se pasa, pero no afecta el almacenamiento en caché
const user = await fetchUser(id);
return <div onClick={onDelete}>{user.name}</div>;
}
Mezcla de contenido dinámico y contenido en caché
- Se puede combinar contenido en caché con contenido dinámico pasándolo como hijos dentro del contenido cacheado
- Se puede especificar un arreglo de
cacheTag para aplicar e invalidar varias etiquetas al mismo tiempo
export async function CachedWrapper({ children }) {
"use cache";
const header = await fetchHeader();
return (
<div>
<h1>{header}</h1>
{children} {/* El contenido dinámico se mantiene tal cual */}
</div>
);
}
export async function ProductPage({ id }) {
"use cache";
cacheTag(["products", `product-${id}`, "featured"]);
// Se puede invalidar usando cualquiera de estas etiquetas
}
Jerarquía de caché
- Si se declara
"use cache" en el nivel superior, toda esa área queda en caché
- Algunas partes específicas (por ejemplo, secciones dinámicas con Suspense) pueden excluirse del área cacheada
"use cache";
export default async function Page() {
return (
<div>
<CachedHeader />
<div>
<Suspense fallback={<Loading />}>
<DynamicFeed /> {/* Contenido dinámico */}
</Suspense>
</div>
</div>
);
}
Seguridad de tipos
- Las cadenas como claves de caché y perfiles de caché se pueden manejar como constantes para reducir el uso de magic strings
- Al usar un enfoque que genere etiquetas, como en el patrón de React Query, resulta más conveniente
// Gestionar claves de perfiles de caché como constantes
export const CACHE_LIFE_KEYS = {
blog: "blog",
} as const;
const config = {
experimental: {
cacheLife: {
[CACHE_LIFE_KEYS.blog]: {
stale: 3600,
revalidate: 900,
expire: 86400,
},
},
},
};
Cómo gestionar eficientemente las etiquetas de caché
- Aplicar el patrón de fábrica de etiquetas al estilo React Query
export const CACHE_TAGS = {
blog: {
all: ["blog"] as const,
list: () => [...CACHE_TAGS.blog.all, "list"] as const,
post: (id: string) => [...CACHE_TAGS.blog.all, "post", id] as const,
comments: (postId: string) =>
[...CACHE_TAGS.blog.all, "post", postId, "comments"] as const,
},
} as const;
// Configurar etiquetas de caché
function tagCache(tags: string[]) {
cacheTag(...tags);
}
// Ejemplo de uso
export async function BlogList() {
"use cache";
tagCache(CACHE_TAGS.blog.list());
}
3 comentarios
Parece que lo mejor es usar frameworks como Next.js o Remix solo cuando el SEO es importante y se necesita SSR.
Especialmente en servicios donde el SEO no es importante, como productos B2B o back office, creo que hay que pensar con cuidado antes de adoptar Next.js. Esto se debe a que las interfaces o la complejidad que impone Next.js pueden reducir la productividad de desarrollo.
Personalmente, creo que cuando el SEO no es necesario, Vite + React es mucho mejor en términos de productividad de desarrollo y flexibilidad.
Next.js se volvió bastante usable desde la versión 13, pero últimamente de verdad me encanta muchísimo. Creo que se va a convertir en el estándar de facto del stack tecnológico para desarrollo web full-stack.