Next.js
Revalidation e invalidación
Explica cómo conectar mutaciones con tags y paths, elegir entre revalidateTag, updateTag y revalidatePath, y mantener consistencia tras el commit.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Next.js
Explica cómo conectar mutaciones con tags y paths, elegir entre revalidateTag, updateTag y revalidatePath, y mantener consistencia tras el commit.
Revalidar no significa “borrar toda la caché”. Significa decidir qué resultados quedaron obsoletos, cuándo deben reemplazarse y si el siguiente lector puede recibir una versión stale mientras se genera otra. La estrategia correcta conecta la semántica de la mutación con las keys y tags que dependen de ella.
Una mutation cambia la fuente autoritativa:
validar y autorizar
↓
commit en base de datos
↓
identificar datos derivados afectados
↓
invalidar tags o paths
↓
usuarios reciben una representación actualizadaLa invalidación debe ocurrir después del commit exitoso. Invalidar antes puede regenerar usando datos antiguos; invalidar aunque la transacción falle crea trabajo sin cambio real.
"use cache";
cacheLife("hours");Aporta cuando puedes tolerar una ventana de obsolescencia y no existe un evento confiable.
cacheTag("products");
revalidateTag("products", "max");Aporta cuando sabes exactamente qué acción o webhook cambió el dato.
Una estrategia común es lifetime largo + invalidación por evento. Así no expiras contenido estable innecesariamente, pero existe un límite de seguridad si el webhook falla.
import { revalidateTag } from "next/cache";
revalidateTag("products", "max");Con el perfil recomendado max, marca entradas como stale y aplica stale-while-revalidate:
entrada marcada stale
↓
lector recibe versión anterior inmediatamente
↓
Next.js regenera en background
↓
lectores posteriores reciben nueva versiónÚsalo para contenido donde una breve inconsistencia es aceptable:
Puede llamarse desde Server Actions y Route Handlers.
import { updateTag } from "next/cache";
updateTag(`cart:${cartId}`);Expira inmediatamente la entrada y está disponible en Server Actions. Está diseñado para read-your-own-writes:
usuario actualiza carrito
↓
commit
↓
updateTag
↓
la siguiente lectura espera dato frescoÚsalo cuando el mismo usuario debe ver el cambio sin una versión stale:
El coste es que la siguiente lectura puede bloquear mientras recalcula.
| API | Semántica | Contexto | Caso |
|---|---|---|---|
| revalidateTag(tag, "max") | Stale-while-revalidate | Action o Route Handler | Contenido compartido |
| updateTag(tag) | Expira inmediatamente | Server Action | Read-your-own-writes |
| revalidatePath(path) | Invalida output/datos asociados a path | Servidor | Ruta sin tags precisas |
| router.refresh() | Solicita nuevo RSC payload | Cliente | Refrescar vista actual |
revalidatePath("/dashboard/orders");Es útil cuando:
Puede invalidar más de lo necesario. Si un producto aparece en home, búsqueda y categoría, revalidar una única ruta puede dejar otras obsoletas. Tags representan mejor dependencias de datos compartidas.
Para un producto:
cacheTag("products");
cacheTag(`product:${productId}`);
cacheTag(`category:${categoryId}:products`);Mutación de precio:
updateTag(`product:${productId}`);
revalidateTag("products", "max");
revalidateTag(`category:${categoryId}:products`, "max");No invalida toda la aplicación. Diferencia la entidad, colecciones y agregados que realmente cambian.
Actualizar una orden puede afectar:
order:{id}
orders:user:{userId}
orders:organization:{organizationId}
dashboard:metrics:{organizationId}
inventory:{productId}La mutation debe conocer o delegar la lista de dependencias. Evita dispersar strings de tags en cada Action; crea helpers:
const orderTags = {
detail: (id: string) => `order:${id}`,
organizationList: (id: string) => `organization:${id}:orders`,
};Esto reduce typos y facilita migraciones.
"use server";
export async function updateProduct(
productId: string,
formData: FormData,
) {
const session = await requireSession();
const input = productSchema.parse(Object.fromEntries(formData));
const product = await productRepository.updateAuthorized({
productId,
organizationId: session.organizationId,
input,
});
updateTag(`product:${product.id}`);
revalidateTag(`organization:${session.organizationId}:products`, "max");
redirect(`/products/${product.slug}`);
}Puede ocurrir:
DB commit exitoso
→ cache invalidation fallaEl dato real cambió, pero la UI puede seguir stale.
Diseña:
No hagas rollback de una transacción de negocio solo porque una optimización de caché falló, salvo que la consistencia inmediata sea requisito contractual.
export async function POST(request: Request) {
const signature = request.headers.get("x-cms-signature");
const rawBody = await request.text();
verifyWebhookSignature(rawBody, signature);
const event = cmsEventSchema.parse(JSON.parse(rawBody));
revalidateTag(`post:${event.slug}`, "max");
revalidateTag("posts", "max");
return Response.json({ revalidated: true });
}Una importación masiva puede disparar miles de tags y regeneraciones.
Estrategias:
Dos Actions modifican el mismo recurso:
A commit → invalidate
B commit → invalidateLa caché debe terminar representando el último estado de la fuente. La invalidación no resuelve lost updates. Usa:
La caché se actualiza después de preservar integridad.
Next.js dispone de una API servidor refresh() para refrescar el router cliente desde una Server Action bajo el modelo actual. No revalida tags por sí sola.
Úsala cuando la mutación afecta datos no cacheados de la vista actual y no necesitas redirect. Para datos cacheados, combina con updateTag o revalidateTag.
Puede ser útil después de una operación externa al sistema de Actions. Pero si no invalida la entrada servidor, obtendrás la misma respuesta.
No lo llames automáticamente después de una Action que ya devuelve árbol actualizado o redirect; puede añadir un request redundante.
El usuario debe ver su cambio inmediatamente.
→ updateTag, lectura dinámica o actualización optimista confirmada.
Una versión stale breve es aceptable.
→ revalidateTag(..., "max").
La fuente se refresca periódicamente.
→ cacheLife.
Elige según producto, no solo rendimiento.
Una tag genérica orders puede invalidar todos los tenants. Usa scope:
organization:{id}:orders
user:{id}:cartNo incluyas PII directa en tags que puedan aparecer en logs. Usa IDs opacos.
Invalidar el servidor no empuja automáticamente el cambio a todos los clientes conectados.
Cada navegador lo verá cuando:
Para colaboración en vivo, necesitas WebSocket, SSE o servicio realtime además de caché.
Prueba el ciclo completo:
Regenera con datos viejos.
Crea trabajo y latencia innecesaria.
Afecta o mezcla scope.
Bloquea lectores cuando SWR era suficiente.
El usuario puede ver stale, contradiciendo la acción.
La invalidación es del servidor, no realtime.
Deja UI obsoleta sin recuperación.
Mutations y Server Actions aplica el orden validate → authorize → transaction → invalidate → redirect o result.