ISR a Escala: Ejecutando 25,000+ Páginas con Incremental Static Regeneration en Vercel

El año pasado lanzamos un sitio Next.js con más de 25,000 páginas generadas estáticamente en Vercel. Páginas de productos, posts de blog, páginas de ubicación, filtros dinámicos de categoría -- todo. La promesa de Incremental Static Regeneration es seductora: obtén la velocidad de sitios estáticos con la frescura del contenido renderizado en servidor. ¿Y honestamente? Mayormente funciona. Pero a 25,000+ páginas, ISR se comporta diferente a como lo hace en tu sitio de marketing de 50 páginas. Los casos edge se convierten en tus casos principales. Los costos se incrementan. Los problemas de invalidación de caché que parecían teóricos en la documentación se vuelven muy, muy reales.

Este es el artículo que desearía que hubiera existido antes de empezar. Todo aquí proviene de experiencia en producción -- métricas reales, sorpresas de facturación reales, y decisiones arquitectónicas reales que tomamos (y a veces nos arrepentimos).

ISR at Scale: Running 25,000+ Pages with Incremental Static Regeneration on Vercel

Tabla de Contenidos

Qué ISR Realmente Hace Bajo el Capó

Antes de entrar en problemas de escala, asegurémonos de que estamos en la misma página sobre qué es ISR. Cuando estableces revalidate: 60 en una página Next.js, aquí está el flujo real:

  1. Primera solicitud después del despliegue: Si la página fue pre-renderizada en tiempo de build, Vercel la sirve desde la caché edge. Si no (devolviste fallback: 'blocking' o usaste dynamicParams: true en App Router), se renderiza del lado del servidor, se cachea el resultado, y luego se sirve.

  2. Solicitudes posteriores dentro de la ventana de revalidación: Servidas desde caché. Rápido. Sin compute.

  3. Primera solicitud después de que expira la ventana de revalidación: La página obsoleta se sirve inmediatamente (esta es la parte "stale-while-revalidate"), y se dispara una regeneración de fondo. El siguiente visitante obtiene la página fresca.

Esto es conceptualmente simple. Pero a 25,000 páginas, ese paso de regeneración de fondo se convierte en una manguera de fuego.

// App Router (Next.js 14/15)
export const revalidate = 60; // seconds

export async function generateStaticParams() {
  // A 25k páginas, probablemente no quieras devolver todas aquí
  const topPages = await getTop500Pages();
  return topPages.map((page) => ({ slug: page.slug }));
}

export default async function ProductPage({ params }: { params: { slug: string } }) {
  const product = await getProduct(params.slug);
  return <ProductTemplate product={product} />;
}

La Compensación de Stale-While-Revalidate

Lo que confunde a la gente: ISR siempre sirve contenido obsoleto a la solicitud que dispara la regeneración. Esta es una característica, no un error -- significa que ningún visitante espera un render. Pero también significa que tu contenido siempre está al menos una solicitud atrás. Para un sitio de 25,000 páginas donde algunas páginas se visitan una vez a la semana, ese "una solicitud atrás" podría significar que alguien vea contenido que tiene días de antigüedad después de que pasó la ventana de revalidación, porque nadie visitó para disparar la regeneración.

Por Qué 25,000 Páginas Lo Cambia Todo

A pequeña escala, ISR es básicamente magia. A gran escala, tres cosas cambian:

Los Tiempos de Build Se Convierten en un Cuello de Botella

Si intentas pre-renderizar las 25,000 páginas en tiempo de build, estás viendo tiempos de build que te harán cuestionarte tus decisiones de vida. Cada página necesita obtener sus datos, renderizar React a HTML, y generar los activos estáticos. Incluso a 200ms por página (lo cual es optimista si estás golpeando una API de CMS), eso son 5,000 segundos -- más de 83 minutos. El plan Pro de Vercel tiene un timeout de build de 45 minutos. Enterprise obtiene más, pero sigues quemando créditos de compute.

La Invalidación de Caché Se Convierte en un Problema Real

Con 25,000 páginas, no puedes simplemente "reconstruir todo" cuando el contenido cambia. Necesitas invalidación quirúrgica. Las APIs revalidatePath() y revalidateTag() de Vercel ayudan, pero tienen sus propias peculiaridades a escala que cubriremos.

Los Picos de Carga de Regeneración de Fondo

Imagina que 5,000 páginas todas tienen revalidate: 60 y todas obtienen tráfico simultáneamente. Eso son 5,000 invocaciones de función serverless sucediendo en el fondo cada minuto. Tu API de CMS mejor que pueda manejar eso.

ISR at Scale: Running 25,000+ Pages with Incremental Static Regeneration on Vercel - architecture

Estrategia de Build: Qué Pre-renderizar vs. Diferir

Esta es la decisión arquitectónica más importante para sitios ISR grandes. Aquí está el marco que usamos:

Categoría de Página Conteo (Nuestro Caso) Estrategia Razonamiento
Páginas de alto tráfico (top 500) 500 Pre-renderizar en build Estas se golpean inmediatamente después del despliegue. Sin penalidad de cold-start.
Páginas de tráfico medio 4,500 Diferir con fallback: 'blocking' El primer visitante espera ~300ms, luego se cachea. Aceptable.
Páginas long-tail 20,000 Diferir con fallback: 'blocking' La mayoría no será visitada por horas/días después del despliegue. Sin sentido pre-renderizar.

La idea clave: no pre-renderices páginas que nadie va a visitar en la primera hora después del despliegue. Estás desperdiciando minutos de build y dinero.

// generateStaticParams - devuelve solo tus páginas de alto tráfico
export async function generateStaticParams() {
  // Usamos datos de analítica para determinar las páginas principales
  const topPages = await fetch('https://api.example.com/pages/top?limit=500', {
    headers: { Authorization: `Bearer ${process.env.CMS_TOKEN}` },
  }).then(r => r.json());

  return topPages.map((page: { slug: string }) => ({
    slug: page.slug,
  }));
}

Con este enfoque, nuestros builds pasaron de hacer timeout a completarse en aproximadamente 8 minutos. Esa es una diferencia masiva. Escribimos sobre estrategias de optimización similares en el contexto de nuestro trabajo de desarrollo Next.js -- los principios aplican ampliamente.

La Configuración de `dynamicParams` Importa

En App Router, establecer dynamicParams = true (el valor por defecto) significa que las páginas no devueltas por generateStaticParams serán renderizadas bajo demanda y cacheadas. Establecerlo en false devuelve un 404 para cualquier página no pre-renderizada. Para un sitio de 25,000 páginas, casi con seguridad quieres true.

export const dynamicParams = true; // Permite renderizado bajo demanda para páginas no en generateStaticParams

Patrones de Revalidación Que Realmente Funcionan

Revalidación Basada en Tiempo

El enfoque más simple. Establece revalidate a un número de segundos. ¿Pero qué número?

Aquí está lo que decidimos después de meses de sintonización:

Tipo de Contenido Período de Revalidación Por Qué
Precios de productos 60 segundos Los precios cambian frecuentemente, los clientes notan precios obsoletos
Descripciones de productos 3600 segundos (1 hora) Raramente cambia, no sensible al tiempo
Posts de blog 86400 segundos (24 horas) Casi nunca cambia después de publicar
Páginas de categoría/listado 300 segundos (5 minutos) Nuevos productos aparecen, pero un pequeño retraso está bien
Páginas de ubicación 86400 segundos (24 horas) La información de dirección apenas cambia

El error que cometimos al principio: establecer todo a 60 segundos. Esto golpeó nuestro API de CMS (Contentful, en nuestro caso) con solicitudes de regeneración y golpeamos límites de velocidad durante picos de tráfico.

Revalidación Bajo Demanda

Este es el mejor enfoque para la mayoría de actualizaciones de contenido. En lugar de hacer polling con revalidación basada en tiempo, disparas la regeneración cuando el contenido realmente cambia:

// app/api/revalidate/route.ts
import { revalidateTag, revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const secret = request.headers.get('x-revalidation-secret');
  
  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });
  }

  const body = await request.json();
  
  // Revalidación basada en tags -- esta es la forma
  if (body.tag) {
    revalidateTag(body.tag);
    return NextResponse.json({ revalidated: true, tag: body.tag });
  }

  // Revalidación basada en ruta como fallback
  if (body.path) {
    revalidatePath(body.path);
    return NextResponse.json({ revalidated: true, path: body.path });
  }

  return NextResponse.json({ error: 'No tag or path provided' }, { status: 400 });
}

Luego configura un webhook en tu CMS para golpear este endpoint siempre que se publique contenido. Combinamos esto con una revalidación basada en tiempo más larga (como 24 horas) como red de seguridad.

Revalidación Basada en Tags a Escala

Aquí es donde Next.js 14+ realmente brilla para sitios grandes. Puedes etiquetar tus solicitudes fetch e invalidar por etiqueta:

async function getProduct(slug: string) {
  const res = await fetch(`https://api.cms.com/products/${slug}`, {
    next: { 
      tags: [`product-${slug}`, 'products', 'all-content'],
      revalidate: 86400 // 24 hour safety net
    },
  });
  return res.json();
}

Ahora cuando se actualiza un único producto, llamas revalidateTag('product-blue-widget') y solo esa página se regenera. Cuando haces una actualización de precio en masa, llama revalidateTag('products') y todas las páginas de productos se regeneran en su próxima visita.

El gotcha: llamar revalidateTag('products') en un sitio con 25,000 páginas de productos no las regenera todas inmediatamente. Las marca todas como obsoletas. Se regeneran en la próxima visita. Esto es importante -- significa que algunas páginas podría no actualizarse durante días si tienen poco tráfico.

Gotchas y Límites Específicos de Vercel

Hemos estado ejecutando esto en Vercel desde principios de 2024. Aquí hay cosas que la documentación no enfatiza lo suficiente:

Almacenamiento en Caché de ISR

Vercel almacena páginas ISR en su caché de Edge Network. A partir de 2025, la Data Cache de Vercel tiene algunos límites que deberías conocer:

  • Plan Pro: La caché ISR incluida es generosa, pero hay un costo para lecturas/escrituras de caché a volumen muy alto
  • Enterprise: Límites personalizados, pero estás pagando por ello
  • Las entradas de caché no viven para siempre: Incluso con revalidate: false, Vercel puede desalojar entradas de caché que no han sido accedidas recientemente. Hemos visto páginas desaparecer de la caché después de aproximadamente 30 días sin tráfico en el plan Pro.

Duración de Función Serverless

La regeneración de fondo se ejecuta como una función serverless. En Vercel Pro, el timeout predeterminado es de 60 segundos (puedes configurar hasta 300 segundos). Si tu página toma más que eso para regenerarse -- digamos, porque tu CMS es lento o estás haciendo procesamiento de imagen pesado -- la regeneración falla silenciosamente y la página obsoleta sigue siendo servida.

Nos golpeó esto con páginas que buscaban datos de tres APIs diferentes. La solución fue agregar una capa de caché (Redis vía Upstash) entre nuestra app Next.js y la API más lenta.

Límites de Regeneración Concurrente

Vercel no publica números duros sobre esto, pero observamos throttling cuando más de ~1,000 regeneraciones ISR se disparaban simultáneamente (p.ej., después de llamar revalidateTag en una etiqueta ampliamente usada). Las regeneraciones se ponen en cola y procesan sobre varios minutos en lugar de todas a la vez. Planifica esto.

Cold Starts

Las páginas que no han sido visitadas en un tiempo (y han sido desalojadas de la caché edge) experimentarán un cold start en la próxima visita. En nuestros benchmarks:

  • Cache hit cálida: 15-40ms TTFB
  • Revalidación obsoleta (servida desde caché): 15-40ms TTFB (igual, ya que obsoleta se sirve)
  • Regeneración fría (sin caché, blocking): 400-1200ms TTFB dependiendo de tiempos de respuesta de API

Costos Reales en Producción a Escala

Hablemos de dinero. Aquí es donde la gente se sorprende.

Nuestro sitio de 25,000 páginas en Vercel Pro ($20/mes base) con ISR:

Componente de Costo Mensual Notas
Suscripción Vercel Pro $20 Plan base
Ejecución de Función Serverless $180-$340 Varía con tráfico. Las regeneraciones ISR cuentan como invocaciones de función.
Ancho de Banda Edge $90-$150 25k páginas con imágenes se suma
Data Cache de Vercel $40-$80 Lecturas/escrituras de caché para ISR
Total Vercel $330-$590/mes Depende del mes de tráfico
Contentful (CMS) $489/mes Su plan Team. Las llamadas a API desde regeneración ISR nos llevaron rápidamente sobre el nivel gratuito.
Upstash Redis (caché) $30/mes Agregado para reducir llamadas a API de CMS
Total General $849-$1,109/mes Para un sitio sirviendo ~2M pageviews/mes

¿Es esto costoso? Comparado con un setup de servidor tradicional, es competitivo. Comparado con un sitio estático en un CDN, es caro. Las invocaciones de función de regeneración ISR son el costo variable más grande -- cada vez que una página se regenera, eso es una función serverless ejecutándose por 1-5 segundos.

Hemos trabajado con clientes que han explorado enfoques basados en Astro para sitios de contenido pesado donde los costos de ISR comienzan a superar sus beneficios. Para sitios donde el contenido cambia infrecuentemente, un build estático completo con Astro puede ser significativamente más barato para alojar.

Monitoreo y Depuración de ISR en Producción

Los fallos de ISR son silenciosos por defecto. La página obsoleta sigue siendo servida, y podrías no saber que tu regeneración ha estado fallando durante días. Aquí está nuestro setup de monitoreo:

Logging Personalizado de Regeneración

// lib/with-regeneration-logging.ts
export async function fetchWithLogging(
  url: string,
  options: RequestInit & { next?: { tags?: string[]; revalidate?: number } }
) {
  const start = Date.now();
  try {
    const res = await fetch(url, options);
    const duration = Date.now() - start;
    
    // Log a tu servicio de monitoreo
    if (duration > 5000) {
      console.warn(`[ISR] Slow fetch: ${url} took ${duration}ms`);
      // Envía a Datadog/Sentry/etc.
    }
    
    return res;
  } catch (error) {
    console.error(`[ISR] Fetch failed: ${url}`, error);
    // Esto es crítico -- si fetch falla, regeneración falla
    throw error;
  }
}

Herramientas Incorporadas de Vercel

El dashboard de Vercel muestra tasas de cache hit de ISR y conteos de regeneración. En la pestaña Analytics, busca:

  • Cache status en los registros de función: HIT, MISS, STALE
  • Duración de regeneración ISR en las métricas de función serverless
  • Tasas de error en tus rutas de ISR

El Encabezado `x-vercel-cache`

Cada respuesta de Vercel incluye este encabezado:

  • HIT -- Servido desde caché edge, fresco
  • STALE -- Servido desde caché edge, regeneración disparada en fondo
  • MISS -- No en caché, renderizado bajo demanda

Configuramos un monitor simple que verifica 100 páginas aleatorias cada hora y alerta si más del 10% devuelven MISS -- eso indicaría problemas de desalojamiento de caché.

Decisiones Arquitectónicas: ISR vs. Alternativas

Después de ejecutar ISR a esta escala durante más de un año, aquí está mi opinión honesta sobre cuándo usarlo y cuándo no:

Usa ISR Cuando:

  • Tienes 5,000-100,000 páginas que cambian a diferentes frecuencias
  • La frescura de contenido medida en minutos (no segundos) es aceptable
  • Ya estás comprometido con Next.js
  • Tu equipo entiende invalidación de caché (no es conocimiento opcional a esta escala)

Considera Alternativas Cuando:

  • Necesitas contenido en tiempo real (usa SSR o fetching del lado del cliente en su lugar)
  • Tu sitio raramente cambia (builds estáticos completos son más simples y baratos)
  • Tienes 500,000+ páginas (ISR comienza a forzarse a muy altos conteos de página -- considera un enfoque de build distribuido)
  • El costo es la preocupación principal (Next.js auto-alojado con tu propio CDN puede ser 60-70% más barato)

Para clientes con arquitecturas de contenido complejas, a menudo recomendamos un setup de CMS sin cabeza que te da flexibilidad para cambiar entre ISR, SSR, y estático completo dependiendo del tipo de contenido.

El Enfoque Híbrido Que Realmente Usamos

No usamos ISR para todo en nuestro sitio de 25k páginas. Aquí está el desglose:

  • ISR: Páginas de productos, páginas de categoría, páginas de ubicación (22,000 páginas)
  • SSR: Resultados de búsqueda, dashboard de usuario, carrito
  • Estático: Acerca de, contacto, páginas legales (generadas en tiempo de build, sin revalidación)
  • Del lado del cliente: Conteos de inventario en tiempo real, precios específicos del usuario

Este enfoque híbrido redujo nuestros costos de función serverless en aproximadamente un 40% comparado con nuestra estrategia inicial "ISR todo".

Benchmarks de Rendimiento De Nuestro Despliegue

Aquí hay números reales de nuestro despliegue en producción, medidos sobre Q1 2025:

Métrica Cache Hit de ISR Cache Miss de ISR (Blocking) SSR Completo (Sin Caché)
TTFB (p50) 22ms 480ms 620ms
TTFB (p95) 58ms 1,100ms 1,450ms
TTFB (p99) 120ms 2,800ms 3,200ms
LCP (p50) 1.1s 1.8s 2.2s
CLS 0.02 0.02 0.05
Tasa de Aprobación de Core Web Vitals 96% 78% 64%

La diferencia entre un cache hit y un miss es dramática. Este es por qué tu estrategia de pre-renderizado importa tanto -- quieres que tus páginas de alto tráfico siempre estén cálidas.

Un hallazgo interesante: nuestras puntuaciones de Core Web Vitals mejoraron un 12% cuando pasamos de revalidate: 60 a revalidate: 3600 en contenido de bajo cambio. Menos regeneraciones significó más cache hits consistentes, lo que significó rendimiento más consistente.

Preguntas Frecuentes

¿Cuántas páginas puede manejar ISR en Vercel antes de que el rendimiento se degrade? Hemos ejecutado 25,000 páginas sin problemas significativos, y he escuchado de despliegues con 100,000+ páginas funcionando bien. El cuello de botella no es el número de páginas en caché -- es la tasa de regeneraciones simultáneas. Si tienes 50,000 páginas todas con revalidate: 60, tendrás problemas. Distribuye tus períodos de revalidación basado en la frecuencia de cambio de contenido y estarás bien.

¿ISR cuesta más que SSR en Vercel? Generalmente, ISR es significativamente más barato que SSR para el mismo volumen de tráfico. Con ISR, la mayoría de solicitudes son servidas desde caché edge (esencialmente compute gratis). Con SSR, cada solicitud ejecuta una función serverless. Para nuestro sitio de 2M pageviews/mes, las invocaciones de función de ISR (de regeneraciones) fueron aproximadamente el 15% de lo que SSR completo habría sido.

¿Qué sucede cuando la regeneración de ISR falla? La versión obsoleta continúa siendo servida. Esto es tanto una característica como un riesgo. Tus usuarios no ven errores, pero podrían ver contenido desactualizado. Hemos tenido situaciones donde una interrupción de API de CMS significó que las páginas sirvieran contenido que tenía 6 horas de antigüedad antes de que alguien lo notara. Configura monitoreo.

¿Puedo usar ISR con el App Router de Next.js? Sí, y es realmente más limpio en App Router. Usas export const revalidate = 60 a nivel de página o layout, y next: { revalidate, tags } en tus llamadas fetch. La función generateStaticParams reemplaza getStaticPaths. Todo lo que hemos descrito en este artículo funciona con ambos Pages Router y App Router, aunque la sintaxis de App Router es lo que recomendaríamos para proyectos nuevos en 2025.

¿Cómo manejo ISR con parámetros dinámicos de consulta? ISR solo cachea basado en la ruta URL, no parámetros de consulta. Si necesitas diferentes versiones en caché para ?color=red vs ?color=blue, necesitas usar segmentos de ruta real (/product/widget/red en lugar de /product/widget?color=red) o manejar la variación del lado del cliente. Esto nos sorprendió con nuestra implementación de filtrado.

¿Es la revalidación bajo demanda confiable a escala? Mayormente. Hemos visto ocasionales retrasos de 10-30 segundos entre llamar revalidateTag() y la caché siendo realmente invalidada en todas las ubicaciones edge. Para el 99% de casos de uso esto está bien. Si necesitas invalidación global instantánea, podrías necesitar agregar parámetros de cache-busting o usar SSR para esas páginas específicas.

¿Debería auto-alojar Next.js en lugar de usar Vercel para sitios ISR grandes? Depende de tu equipo. El auto-alojamiento (en AWS, por ejemplo) te da más control sobre el comportamiento de caché y puede ser 50-70% más barato a escala. Pero eres responsable de configurar la invalidación de caché de CDN, manejar el pipeline de build, y gestionar la distribución edge por ti mismo. Hemos visto equipos gastar meses replicando lo que Vercel te da fuera de la caja. Si quieres explorar opciones, contáctanos -- hemos hecho ambas.

¿Cuál es el mejor CMS para un sitio ISR de 25,000+ páginas? Hemos usado Contentful, Sanity, e Hygraph a esta escala. Contentful maneja revalidación basada en webhook bien pero los límites de velocidad pueden ser un problema (planifica caché). Las suscripciones GROQ de Sanity son excelentes para conciencia de cambios de contenido en tiempo real. El sistema de webhook de Hygraph es sólido. El requisito clave es entrega de webhook confiable y una API que pueda manejar tráfico de ráfaga desde tormentas de regeneración. Verifica nuestras capacidades de desarrollo de CMS sin cabeza para recomendaciones más específicas basadas en tu modelo de contenido.