ISR Strategies en Next.js: La Decisión de Renderizado que Separa el Rendimiento del Caos
Estrategias de renderizado en Next.js: Static vs ISR vs Dynamic. Framework de decisión, configuración práctica y optimización de rendimiento en Vercel.
El 90% de los Developers Elige Su Estrategia de Renderizado Mal
Tienes una página de producto en tu e-commerce. 10.000 visitas diarias. Precio que cambia cada hora.
La configuras como Static Generation.
Vercel cachea el HTML. El tiempo de respuesta es instantánea. Todo parece perfecto.
Hasta que un usuario ve un precio de ayer.
*El problema real no es qué herramienta usas. Es que no entiendes cuándo invalidate el cache.*
La mayoría de developers elige entre Static, ISR y Dynamic basándose en intuición. Pocos han leído la documentación de revalidate. Menos todavía han medido el impacto real de cada decisión.
Este artículo te da el framework para decidir correctamente.
Por Qué Static No Siempre Es la Respuesta
La sabiduría convencional dice: "Static es más rápido, úsalo siempre que puedas."
Esa frase es mentira incompleta.
Static Generation genera HTML en build time. El contenido nunca cambia hasta el siguiente deploy. Para páginas de marketing, documentación, o landing pages, es perfecto.
Para tu catálogo de productos con precios que fluctúan, es un desastre.
El espectro completo de renderizado en Next.js
```
┌─────────────────────────────────────────────────────────────┐
│ STATIC ISR SSR DYNAMIC │
│ (0ms TTFB) (millones visits) (per-request) (always-fresh)│
│ ↑ ↑ ↑ ↑ │
│ build time revalidate runtime runtime │
└─────────────────────────────────────────────────────────────┘
```
La decisión correcta depende de tres variables:
→ Frecuencia de cambio del contenido
→ Volumen de tráfico esperado
→ Necesidad de datos personalizados por usuario
ISR: El Medio Que Todos Ignoran
La Incremental Static Regeneration existe desde Next.js 12.1.
Funciona así: generas la página estática en build, pero defines un intervalo de revalidación. Pasado ese tiempo, la siguiente petición triggers una regeneración en background.
```javascript
// app/productos/[slug]/page.tsx
export async function generateStaticParams() {
const productos = await fetch('https://api.tienda.com/productos')
.then(res => res.json())
return productos.map((producto) => ({
slug: producto.slug,
}))
}
export const revalidate = 3600 // Revalidar cada hora
export default async function ProductoPage({ params }) {
const producto = await fetch(
`https://api.tienda.com/productos/${params.slug}`,
{ next: { revalidate: 3600 } }
).then(res => res.json())
return (
<main>
<h1>{producto.nombre}</h1>
<p>Precio: {producto.precio}€</p>
{/ resto del producto /}
</main>
)
}
```
Este código genera estáticamente todas las páginas de productos en build. Después, cada hora, la primera visita que llega dispara una regeneración en background.
Los números que importan
TTFB (Time to First Byte): similar a Static una vez cacheado
Freshness: contenido actualizado dentro del intervalo de revalidate
Server load: una regeneración por hora, no por request
Scalability: maneja millones de visitas sin computational overhead
ISR te da 90% de los beneficios de Static con 10% de la frescura de Dynamic.
*El secreto es elegir el intervalo de revalidate correcto.*
El Framework de Decisión: Tres Preguntas
Antes de elegir tu estrategia, responde estas tres preguntas en orden:
1. ¿Con qué frecuencia cambia el contenido?
```
< 1 hora → Dynamic Rendering
1-24 horas → ISR con revalidate bajo
1-7 días → ISR con revalidate medio
> 7 días → Static Generation
```
2. ¿El contenido es personalizado por usuario?
```
Sí, diferente para cada usuario → Dynamic o SSR
Sí, pero con segmentos comunes → ISR + cookies/headers
No, igual para todos → Static o ISR
```
3. ¿Cuánto tráfico esperas?
```
< 100k visitas/mes → Cualquiera funciona
100k-1M visitas/mes → ISR o Static (optimiza costs)
> 1M visitas/mes → Static o ISR (minimiza server load)
```
❌ Lo que hace la mayoría:
"Elijo ISR porque es el término del medio y parece equilibrado."
✅ Lo correcto:
"Aplico el framework: contenido cambia cada 6 horas, es igual para todos, espero 500k visitas → ISR con revalidate 21600."
Patrones de ISR Que el Tutorial Básico No Entiende
ISR por-ruta vs global
Next.js 14+ permite configurar revalidate a nivel de segmento completo o por-route específica:
```javascript
// app/layout.tsx - revalidate global para toda la app
export const revalidate = 3600
// app/blog/[slug]/page.tsx - override para blog
export const revalidate = 86400 // 24 horas para blog
// Componente individual con fetch específico
const producto = await fetch(url, {
next: { revalidate: 1800 } // 30 min solo para este fetch
})
```
On-Demand Revalidation
El revalidate basado en tiempo tiene un problema: si publicas un artículo a las 23:59 y el revalidate está en 24 horas, el contenido viejo permanece hasta el día siguiente.
La solución es on-demand revalidation via webhooks:
```javascript
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache'
export async function POST(request) {
const body = await request.json()
const secret = request.headers.get('x-revalidate-secret')
if (secret !== process.env.REVALIDATE_SECRET) {
return Response.json({ error: 'Invalid secret' }, { status: 401 })
}
// Revalidar una ruta específica
if (body.path) {
revalidatePath(body.path)
return Response.json({ revalidated: true, path: body.path })
}
// Revalidar por tag (más granular)
if (body.tag) {
revalidateTag(body.tag)
return Response.json({ revalidated: true, tag: body.tag })
}
}
```
Tu CMS (Sanity, Contentful, Strapi) envía un webhook a `/api/revalidate` cuando publicas contenido. Vercel recibe el webhook, triggerea revalidación, y el contenido se actualiza instantáneamente.
Stale-While-Revalidate Pattern
Para contenido crítico donde la frescura importa más que la velocidad, usa el patrón stale-while-revalidate:
```javascript
export default async function DashboardPage() {
// Siempre sirve contenido stale inmediatamente
// Mientras tanto, revalida en background
const datos = await fetch('https://api.com/dashboard', {
next: {
revalidate: 60,
tags: ['dashboard'] // Para on-demand posterior
}
}).then(res => res.json())
return <Dashboard data={datos} />
}
```
El usuario ve contenido inmediatamente (puede ser 1 minuto viejo). En background, Next.js revalida. La siguiente petición ya tiene contenido fresco.
Cuándo Dynamic Rendering Es La Elección Correcta
No todo debe ser ISR.
Dynamic Rendering genera HTML en cada request. Es slower, pero necesario en tres escenarios:
1. Contenido personalizado por sesión
```javascript
export const dynamic = 'force-dynamic'
export default async function ProfilePage() {
const session = await getServerSession()
const usuario = await getUsuario(session.user.id)
return (
<div>
<h1>Bienvenido, {usuario.nombre}</h1>
<p>Tu plan: {usuario.plan}</p>
</div>
)
}
```
2. Datos que cambian en tiempo real
```javascript
// Pricing con descuentos limitados
export const dynamic = 'force-dynamic'
export default async function PricingPage() {
// Precios que cambian constantemente
const precios = await fetch('https://api.com/precios-actuales', {
cache: 'no-store' // No cachear nunca
}).then(res => res.json())
return <Pricing precios={precios} />
}
```
3. Headers/Cookies requeridos
```javascript
export const dynamic = 'force-dynamic'
export default async function ABMasivoPage({ request }) {
const abVariant = request.headers.get('x-ab-variant')
// Contenido varía según test A/B
return <Landing variant={abVariant} />
}
```
❌ Error común:
"Uso Dynamic para todo porque es más simple y no tengo que pensar en cache."
✅ Decisión correcta:
"Mi dashboard de analytics tiene datos real-time → Dynamic. Mi página de pricing con cambios semanales → ISR."
Configuración en Vercel: El Detalle Que Importa
Vercel automatiza mucho del cacheo, pero hay configuraciones específicas que debes conocer:
```json
// vercel.json - configuración de headers de cache
{
"headers": [
{
"source": "/precios/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=0, s-maxage=3600, stale-while-revalidate=86400"
}
]
}
]
}
```
Headers de Cache en Vercel
```
s-maxage: Control de cache en CDN de Vercel
stale-while-revalidate: Servir stale mientras revalida en background
```
Para páginas ISR con revalidate 3600:
```
s-maxage=3600, stale-while-revalidate=86400
```
El CDN cachea 1 hora. Pasada esa hora, sirve stale hasta 24 horas mientras revalida.
Caso Real: Migración de Dynamic a ISR
Proyecto real: e-commerce de moda con 800k visitas mensuales.
Situación inicial:
Todo en Dynamic Rendering
TTFB promedio: 850ms
Server load: alto, costs elevados
Contenido de producto actualizable 2-3 veces por semana
Cambio aplicado:
Categorías y listados → ISR con revalidate 3600
Páginas de producto → ISR con revalidate 300 + on-demand revalidation
Carrito y checkout → Dynamic (sesión personalizada)
Landing pages → Static Generation
Resultados medidos:
TTFB promedio: 45ms (95% improvement)
Server load: reducido 73%
Contenido de producto actualizado: instantáneo post-publicación
Coste de infraestructura: significativamente menor
La diferencia no fue cambiar de hosting. Fue elegir la estrategia de renderizado correcta.
Key Takeaways
→ Static Generation es para contenido que nunca cambia: documentación, landing pages, contenido de marketing.
→ ISR es para contenido que cambia periódicamente con volumen alto: catálogos, blogs, listados de productos.
→ Dynamic Rendering es para contenido personalizado o real-time: dashboards, perfiles de usuario, precios que fluctúan constantemente.
→ On-demand revalidation elimina la necesidad de intervalos cortos de revalidate. Tu CMS triggerea la actualización cuando publicas.
→ Mide antes de optimizar. Dashboard de Vercel te muestra TTFB, cache hit rate, y server execution time. Esas métricas te dicen si tu estrategia actual funciona.
Tu Siguiente Paso
Abre tu proyecto Next.js en Vercel. Identifica tus 10 páginas con más tráfico. Clasifícalas:
1. ¿Con qué frecuencia cambia el contenido?
2. ¿Es igual para todos los usuarios?
3. ¿Cuál es tu TTFB actual?
Si alguna página con contenido estático tiene TTFB > 100ms, probablemente está en Dynamic cuando debería estar en ISR.
La decisión de renderizado no es arquitectura fija. Es una decisión por página que evoluciona con tu producto.
Cambia una página hoy. Mide el impacto. Repite.
Lee el artículo completo en brianmenagomez.com
Más sobre mis servicios en brianmenagomez.com
Herramientas: Conversor IAE CNAE · Gestorias cerca de ti · Calculadora IRPF

