Headless WordPress, ISR o SSR: elegir el modo de renderizado según el ritmo del contenido
La pregunta “ISR o SSR” solo tiene sentido ruta por ruta. No hay una respuesta para todo el sitio. Astro y Next.js permiten elegir el modo a nivel de página o de layout, y lo que hace un equipo con experiencia es decidir de forma deliberada, ruta por ruta, a partir de un modelo de cada cuánto cambia el contenido.
Este artículo forma parte del pilar del servicio headless WordPress y complementa la matriz de decisión Next.js o Astro, que trata la elección a nivel de framework.
En resumen
- ISR (o estático con revalidación) gana cuando el ritmo de cambios es previsible y el tráfico es alto.
- SSR gana cuando la página está personalizada, depende de la sesión o contiene datos en directo.
- La corrección de ISR depende de la invalidación de caché; los webhooks son mejores que la revalidación por tiempo.
- Cloudflare Workers puede servir ambos a través del adaptador del framework; una página en caché apenas consume CPU, SSR paga el renderizado completo.
- El punto de partida es el modo más barato que da un resultado correcto; pasa a SSR solo cuando haga falta.
SSG, ISR y SSR explicados para WordPress
Generación de sitios estáticos (SSG). La página se construye una vez, en el build, y se sirve como HTML plano. La más barata en cada petición, la más lenta de actualizar.
Incremental Static Regeneration (ISR). La página se construye una vez, pero puede regenerarse ante un disparador, normalmente un webhook al publicar o un intervalo de revalidación por tiempo. Barata en cada petición, con consistencia eventual en las actualizaciones.
Server-Side Rendering (SSR). La página se renderiza en cada petición. Siempre actualizada, pero el coste de ejecución crece con el tráfico. La personalización, la autenticación y los datos en directo encajan aquí de forma natural.
En WordPress headless, los tres modos leen del origen WordPress mediante REST o GraphQL. La diferencia está en cuándo leen.
Cuándo usar ISR y cuándo SSR en WordPress headless
Importan dos factores:
Ritmo de cambios del contenido. ¿Con qué frecuencia cambia esta página? ¿Una vez al trimestre, una vez al día, cada minuto, en tiempo real?
Superficie de personalización. ¿La página cambia según el visitante? Estado de sesión iniciada, precios según la ubicación, variante de un test A/B.
La regla: elige el modo más barato que dé un resultado correcto. El estático es el más barato. SSR, el más caro. Avanza hacia SSR solo cuando un modo más barato no dé un resultado correcto.
| Tipo de página | Modo por defecto | Por qué |
|---|---|---|
| Páginas de marketing, entradas del blog | Estático (reconstrucción al publicar) | Pocos cambios, sin personalización |
| Archivos de categorías y etiquetas | ISR con webhook de publicación | El ritmo depende de la publicación de contenido |
| Páginas de producto, catálogo estable | ISR con webhook de stock | Invalidación previsible |
| Páginas de producto, stock en tiempo real | SSR con caché en el edge | El stock cambia en segundos |
| Carrito y checkout | SSR | Dependen de la sesión por definición |
| Panel con sesión iniciada | SSR | Estado por usuario |
| Portada editorial | ISR con webhook de publicación | El ritmo depende de los eventos de publicación |
Invalidación de caché ISR con webhooks de WordPress
ISR parece gratis hasta que sirve una URL canónica obsoleta tras un cambio de slug. El patrón que lo evita:
Invalidación por webhooks. El origen WordPress envía un webhook al publicar, al cambiar un slug o al borrar una entrada. El framework de front-end recibe el webhook y dispara la regeneración de las páginas afectadas. El coste es una integración de webhook en el origen WordPress, que se paga una sola vez.
Revalidación por tiempo solo como red de seguridad. Un intervalo de revalidación de 60 segundos cubre los fallos de entrega de webhooks, pero no debería ser el disparador principal. Un intervalo de 60 segundos no reconstruye cada página 60 veces por hora: Next.js regenera una página solo cuando llega una petición después de que venza la ventana. El coste real es que cada página popular puede regenerarse en segundo plano hasta una vez por minuto, y en plataformas que facturan por petición ese trabajo en segundo plano cuenta como cómputo adicional.
Etiquetas de caché, no URL. Cada página en caché se etiqueta con el ID de la entrada en WordPress, los ID de los términos a los que hace referencia y etiquetas transversales (portada, sitemap). Cuando llega un webhook, el front-end purga por etiqueta, no por URL. Es la diferencia entre “regenerar la página del producto” (frágil) y “regenerar todo lo que hace referencia al producto 8421” (correcto).
Coste de ISR y SSR en Cloudflare Workers
Astro llega a Workers a través de su adaptador oficial de Cloudflare. Next.js documenta ISR para el runtime de Node.js y describe el soporte mediante adaptadores como dependiente de la plataforma, así que conviene revisar el adaptador antes de planificar ISR en Workers. El coste por modo:
- Estático en el edge. Cloudflare Pages sirve HTML plano casi sin CPU por petición. El modo más barato.
- ISR. La primera petición tras la invalidación paga el coste completo de renderizado; las peticiones en caché, casi nada. En Workers esto depende de la caché del adaptador, no solo del framework.
- SSR. Cada petición paga en Workers el coste completo de renderizado. Previsible por petición, caro a escala.
La diferencia de coste importa con mucho tráfico. Con poco tráfico, decide la corrección, no el coste.
Ejemplos de ISR y SSR en rutas reales de WordPress
Portada de marketing. Estática, reconstruida por webhook en cada publicación editorial. Caché de 24 horas en el edge con opción de purga manual. SSR como alternativa solo si se añade un banner específico por país.
Ficha de producto en WooCommerce. ISR con el ID del producto como clave. Webhook de WooCommerce ante cambios de stock, de precio o de contenido. Ventana de caché: 1 hora como red de seguridad. SSR solo si mostrar el stock en tiempo real es un requisito de UX.
Historial de pedidos del cliente. SSR. Por usuario, dependiente de la sesión, sin caché en el edge.
La misma arquitectura, tres modos de renderizado distintos, una sola regla de decisión.
Cómo funciona revalidate de Next.js en páginas de WordPress
Una ruta con export const revalidate = 3600 sirve su HTML en caché a cada petición dentro de la ventana. La guía de ISR de Next.js vigente describe lo que ocurre después: la primera petición tras vencer la ventana sigue recibiendo la página obsoleta, esa petición inicia una regeneración en segundo plano y, cuando termina bien, la siguiente petición recibe la versión nueva. Una entrada que nadie visita no se regenera nunca. El intervalo limita la frecuencia con la que una página puede reconstruirse; no programa reconstrucciones.
Cuatro detalles de la misma guía deciden si esto funciona en un front de WordPress:
- Cuando una ruta hace varias llamadas fetch con valores de
revalidatedistintos, el más bajo fija el intervalo de ISR de la página. Una barra lateral que carga las últimas entradas con una ventana de 60 segundos arrastra todo el artículo a 60 segundos. - Un solo fetch con
revalidate: 0ocache: 'no-store'convierte la ruta en dinámica. Una única llamada sin caché a un endpoint de WordPress, como un contador de visitas, pasa la página de ISR a SSR sin avisar. - Si la regeneración falla, por ejemplo porque el origen WordPress devuelve un error, Next.js sigue sirviendo la última versión correcta y lo reintenta en la siguiente petición. Una caída del origen no deja el front en blanco.
- La guía recomienda un intervalo largo, una hora mejor que un segundo, y revalidación bajo demanda cuando hace falta precisión. Es la regla de webhook primero de la sección anterior.
La cabecera de respuesta x-nextjs-cache indica el estado de cada petición como HIT, STALE, MISS o REVALIDATED. Leerla justo después de publicar es la forma más rápida de confirmar que un webhook ha llegado al front.
Cómo revalidar páginas de Next.js con un webhook de WordPress
El extremo receptor es un Route Handler, por ejemplo app/api/revalidate/route.ts, que comprueba un secreto compartido y llama a una de dos funciones de next/cache.
revalidateTag(tag, 'max') marca como obsoleto cada fetch en caché con esa etiqueta, en todas las páginas que lo usan (referencia de revalidateTag). Con el perfil 'max', los visitantes siguen recibiendo la copia antigua mientras se carga la nueva. Para una publicación en WordPress suele ser el equilibrio correcto: se etiquetan las llamadas fetch con post-8421 y posts, y un solo webhook refresca la entrada, el archivo y la portada. Cuando la copia antigua no debe servirse nunca más, como en un artículo retirado, la referencia indica a quien llama desde un webhook que pase { expire: 0 }, porque updateTag solo funciona dentro de Server Actions. La forma con un solo argumento, revalidateTag(tag), está obsoleta. Las etiquetas distinguen mayúsculas y minúsculas y tienen un límite de 256 caracteres.
revalidatePath(path) actúa sobre rutas en lugar de datos (referencia de revalidatePath). Una ruta literal como /blog/my-post refresca una página; un patrón como /blog/[slug] necesita el segundo argumento, 'page' o 'layout'. En WordPress headless hay dos trampas frecuentes. Con rewrites en next.config.js, hay que pasar la ruta de destino, no la URL que ve el visitante. Y Proxy no se ejecuta en las peticiones de ISR bajo demanda, así que una ruta reescrita ahí debe revalidarse en su forma final.
Las dos funciones solo marcan entradas; la regeneración ocurre en la siguiente visita. Cuando la aplicación corre en varias instancias con la caché de sistema de archivos por defecto, un webhook invalida solo la instancia que lo recibe. La revalidación bajo demanda necesita entonces un cache handler compartido antes de poder fiarse de ella.
Cómo hacer ISR en Astro
Astro prerrenderiza todas las páginas por defecto. Una ruta pasa al renderizado bajo demanda con export const prerender = false, lo que exige un adaptador de servidor. output: 'server' invierte el valor por defecto, y la documentación aconseja quedarse en 'static' hasta que la mayoría de las páginas necesiten de verdad un servidor (renderizado bajo demanda en Astro). Una página renderizada bajo demanda puede fijar su propia cabecera Cache-Control con Astro.response.headers.
Astro 7 añade route caching, que cierra la mayor parte de la distancia con el ISR de Next.js (route caching en Astro). Una página bajo demanda llama a Astro.cache.set({ maxAge: 3600, swr: 60, tags: ['post-8421'] }). El endpoint que recibe el webhook de WordPress llama a context.cache.invalidate({ tags: ['post-8421'] }) o a invalidate({ path: '/blog/my-post' }). La invalidación por ruta solo actúa con coincidencia exacta, sin comodines. Las routeRules de astro.config.mjs fijan valores por defecto para grupos de rutas, por ejemplo un maxAge para /blog/[...slug], y una página puede sobrescribirlos o excluirse con Astro.cache.set(false) cuando la respuesta está personalizada.
La caché en sí la aporta un proveedor. El memoryCache() integrado sirve para un único servidor Node. Los adaptadores de Netlify, Vercel y Cloudflare incluyen proveedores CDN que siguen siendo experimentales y hay que activar a mano. El proveedor de Cloudflare, desde @astrojs/cloudflare 14, fija las cabeceras Cloudflare-CDN-Cache-Control y Cache-Tag y activa la caché de Workers. En desarrollo la API de caché no hace nada, así que hay que probarla con un build y una vista previa.
Cómo combinar partes estáticas y dinámicas en una página de WordPress
Muchas rutas son estables salvo por un elemento en vivo: una página de producto con el stock, un artículo con el avatar del lector conectado. Renderizar la página entera en cada petición por ese único elemento es la solución cara. Las server islands de Astro son la barata (server islands en Astro). Se añade server:defer al componente que necesita datos frescos. El resto de la página sigue prerrenderizado o en caché, y la isla se pide a su propio endpoint después de cargar la página. Mientras tanto se muestra en su lugar un hijo con slot="fallback", y cada isla carga por separado, así que una consulta lenta de stock no retrasa nada más.
Hay dos límites. Las server islands necesitan un adaptador, y sus props deben ser serializables: objetos simples, cadenas, números, arrays, fechas y algunos tipos más, pero no funciones. En una página de producto de WooCommerce eso significa pasar el ID del producto, no un cliente de la API.
Esto afina la tabla anterior. La ruta sigue siendo estática o ISR, y solo la isla se renderiza en cada petición.
Cómo obtener contenido de WordPress para builds estáticos e ISR
Cada paso de prerrenderizado empieza con una lista de slugs, y la REST API de WordPress la devuelve por páginas. per_page acepta valores de 1 a 100, y cada respuesta lleva las cabeceras X-WP-Total y X-WP-TotalPages, de modo que un script de build lee la primera página y recorre hasta el número de páginas que indica la cabecera (paginación en la REST API). Next.js pasa esa lista a generateStaticParams. Las entradas publicadas después del build se renderizan en su primera petición salvo que dynamicParams sea false, y generateStaticParams no se vuelve a llamar durante la revalidación (referencia de generateStaticParams).
El parámetro _fields recorta la respuesta a lo que necesita el paso, por ejemplo ?_fields=slug,modified_gmt para la lista de slugs, y WordPress no llega a calcular los campos omitidos. Desde WordPress 5.3 acepta también claves meta anidadas, y desde 5.4 _embed se puede limitar a relaciones con nombre como author,wp:term (parámetros globales de la REST API).
Para detectar cambios, el endpoint de entradas acepta modified_after con una fecha ISO 8601 y devuelve modified_gmt en cada entrada (referencia del endpoint posts). Una tarea programada que pide las entradas modificadas desde su última ejecución es una buena red de seguridad para webhooks perdidos, y más barata que una ventana corta de revalidate en todas las rutas.
Cómo previsualizar borradores de WordPress en un front con ISR
Los editores esperan que la vista previa muestre el borrador, mientras que un front en caché sirve la versión publicada. Draft Mode de Next.js lo resuelve por visitante (guía de Draft Mode). Un Route Handler comprueba un secreto compartido, llama a draftMode().enable(), que fija la cookie __prerender_bypass, y redirige a la página. Las peticiones con esa cookie se saltan la caché de fetch y la caché de respuestas de ISR y se envían con Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate. El resto de visitantes sigue recibiendo la página en caché.
En el lado de WordPress, el endpoint de entradas devuelve contenido publicado salvo que se indique otra cosa, porque status vale publish por defecto. Los borradores se leen, por tanto, con peticiones autenticadas. Las contraseñas de aplicación, integradas en WordPress desde la 5.6 y enviadas como Basic Auth sobre HTTPS, son el método que documenta el manual (autenticación en la REST API). Esa credencial debe quedarse en el servidor, en el Route Handler o en un fetch que solo se ejecute en el servidor, y nunca en el código del cliente.
Guías relacionadas sobre WordPress headless
Este artículo forma parte del pilar del servicio headless WordPress. Para la elección a nivel de framework, consulta la matriz de decisión Next.js o Astro. Los riesgos de una migración están en la lista de patrones SEO para WordPress headless, lo específico de una tienda en WordPress headless para WooCommerce y la parte de costes en el artículo sobre la economía de WordPress headless. Para una comparación de los frameworks versión a versión, incluida la forma en que cada uno carga los datos de WordPress, consulta Astro 5 vs Next.js 15.
La parte de implementación de este tema la llevamos dentro de la auditoría Core Web Vitals.







