BlogCache Components

Ultima actualización

Next.js 16 ofrece dos formas de invalidar cache: revalidateTag y updateTag. Ambos funcionan con tags, pero tienen comportamientos diferentes.

updateTag vs revalidateTag

Stale-while-revalidate

app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'

export async function revalidatePrice(productId: string) {
  revalidateTag(`product-price-${productId}`, 'max')
  //                                            ↑
  //                                    Perfil de revalidación
}

Comportamiento

  1. Marca el cache como "stale" (obsoleto)
  2. Sigue sirviendo contenido viejo mientras regenera en background
  3. Próxima request obtiene contenido nuevo

Timeline

t=0s  User 1 clicks "Revalidar"
      → Cache marcado como stale
      → Sigue sirviendo versión vieja

t=1s  User 2 visita página
      → Recibe versión vieja
      → Trigger regeneración en background

t=3s  Regeneración completa

t=5s  User 3 visita página
      → Recibe versión NUEVA ✨

Perfiles disponibles

revalidateTag('my-tag', 'max')      // Más agresivo
revalidateTag('my-tag', 'default')  // Balance
revalidateTag('my-tag', 'min')      // Más conservador

// O custom
revalidateTag('my-tag', {
  stale: 3600,
  revalidate: 7200,
  expire: 86400
})

Usa cuando

✅ El cambio no es crítico inmediato
✅ Puedes tolerar data vieja brevemente
✅ Blog posts, precios, contenido editorial
✅ Quieres minimizar latencia para usuarios

Ejemplo real

app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'

// Actualizar precio de producto
export async function updateProductPrice(
  productId: string, 
  newPrice: number
) {
  // 1. Actualizar en DB
  await db.query(
    'UPDATE products SET price = ? WHERE id = ?',
    [newPrice, productId]
  )
  
  // 2. Revalidar cache (stale-while-revalidate)
  revalidateTag(`product-price-${productId}`, 'max')
  
  return { success: true }
}

Comparación directa

AspectorevalidateTagupdateTag
ComportamientoStale-while-revalidateInvalidación inmediata
LatenciaBaja (sirve viejo primero)Media (regenera en request)
ConsistenciaEventualInmediata
PerformanceMejorBuena
Caso de usoContenido editorialMutaciones de usuario
Segundo argumentoSí (perfil)No

Ejemplos de casos de uso

Blog / Contenido editorial

app/actions.ts
"use server";
import { revalidateTag } from "next/cache";

export async function publishBlogPost(postId: string) {
  await db.query("UPDATE posts SET published = true WHERE id = ?", [postId]);

  // Stale-while-revalidate: OK que algunos vean versión vieja
  revalidateTag(`blog-post-${postId}`, "max");
  revalidateTag("blog-list", "max");
}

E-commerce / Carrito

app/actions.ts
"use server";
import { updateTag } from "next/cache";

export async function addToCart(userId: string, productId: string) {
  await db.query("INSERT INTO cart_items (user_id, product_id) VALUES (?, ?)", [
    userId,
    productId,
  ]);

  // Invalidación inmediata: usuario debe ver su carrito actualizado YA
  updateTag(`user-cart-${userId}`);
}

Sistema de likes

app/actions.ts
"use server";
import { updateTag } from "next/cache";

export async function likePost(userId: string, postId: string) {
  await db.query("INSERT INTO likes (user_id, post_id) VALUES (?, ?)", [
    userId,
    postId,
  ]);

  // Invalidación inmediata: usuario debe ver su like reflejado
  updateTag(`post-likes-${postId}`);
  updateTag(`user-liked-posts-${userId}`);
}

Precios de productos

app/actions.ts
"use server";
import { revalidateTag } from "next/cache";

export async function updatePrice(productId: string, newPrice: number) {
  await db.query("UPDATE products SET price = ? WHERE id = ?", [
    newPrice,
    productId,
  ]);

  // Stale-while-revalidate: OK que el precio tarde un poco en actualizarse
  revalidateTag(`product-price-${productId}`, "max");
}

Revalidar múltiples tags

Ambas funciones se pueden llamar múltiples veces:

app/actions.ts
"use server";
import { revalidateTag, updateTag } from "next/cache";

export async function updateProduct(productId: string, data: ProductData) {
  await db.query("UPDATE products SET ... WHERE id = ?", [productId]);

  // Combinar ambos enfoques según necesidad
  revalidateTag(`product-text-${productId}`, "max"); // Texto: stale-while-revalidate
  revalidateTag(`product-price-${productId}`, "max"); // Precio: stale-while-revalidate
  updateTag(`product-inventory-${productId}`); // Inventario: inmediato
}

revalidatePath vs Tags

revalidatePath (tradicional)

revalidatePath("/product/1");

Invalida: Toda la ruta /product/1

Problema: No puedes invalidar campos específicos

Tags (granular)

revalidateTag("product-price-1", "max");

Invalida: Solo el cache del precio del producto 1

Ventaja: Quirúrgico, no afecta otros campos

Regla general: Usa tags para control granular. Solo usa revalidatePath cuando quieras invalidar toda una página.

Debugging revalidación

Logs útiles

app/actions.ts
"use server";
import { revalidateTag } from "next/cache";

export async function revalidatePrice(productId: string) {
  console.log(
    `[Revalidation] product-price-${productId} at ${new Date().toISOString()}`
  );
  revalidateTag(`product-price-${productId}`, "max");
}

Verificar en browser

  1. Click en "Revalidar"
  2. Refresh la página
  3. View Source (Ctrl+U)
  4. Busca el contenido actualizado en el HTML

Si el contenido cambió, la revalidación funcionó.

Headers de respuesta

Next.js incluye headers útiles:

x-nextjs-cache: HIT | MISS | STALE
  • HIT - Contenido del cache
  • MISS - Regenerado
  • STALE - Stale-while-revalidate en progreso

Mejores prácticas

1. Usa el approach correcto

// ✅ Contenido editorial
revalidateTag("blog-post", "max");

// ✅ Mutaciones de usuario
updateTag("user-cart");

2. Tags descriptivos

// ✅ Bueno
cacheTag(`product-price-${productId}`);
cacheTag(`user-profile-${userId}`);

// ❌ Malo
cacheTag("data", id);
cacheTag("cache1", value);

3. Revalidar relacionados

// Actualizar producto → revalidar múltiples tags
revalidateTag(`product-${productId}`, "max");
revalidateTag("product-list", "max");
revalidateTag(`category-${categoryId}`, "max");

4. Batch operations

// Si actualizas múltiples productos, agrúpalos
export async function updateMultipleProducts(productIds: string[]) {
  await db.transaction(async (tx) => {
    // Updates en transaction
  });

  // Revalidar todos juntos
  productIds.forEach((id) => {
    revalidateTag(`product-${id}`, "max");
  });
}

Performance: Demasiadas revalidaciones simultáneas pueden causar load. Usa con moderación.

En esta página