wandres.dev
SSR, SSG E HÍBRIDO · output y prerender

prerender por página: el híbrido fino

La exportación `export const prerender` es el interruptor más fino del renderizado: una constante a nivel de módulo que el compilador lee estáticamente para elegir, ruta por ruta, si una página se hornea o se sirve viva. Cómo invertir el valor por defecto, cuándo prerenderizar, y qué información impide congelar una página.

⏱ 16 min

Si output fija el momento de render por defecto de todo el proyecto, export const prerender es la pieza que convierte ese eje global en un mapa detallado. Es el interruptor más fino que Astro ofrece: una constante que cada página exporta para declarar su propio momento, con independencia de lo que diga la configuración. Aquí es donde el híbrido deja de ser una palabra y se vuelve un dibujo concreto —qué rutas se hornean, cuáles se sirven vivas— trazado a mano, página por página, según lo que cada una necesita de verdad.

🎯 Al terminar esta lección sabrás
  • Declarar export const prerender y entender que el compilador lo lee de forma estática.
  • Invertir el valor por defecto de output ruta por ruta, en ambas direcciones.
  • Reconocer qué páginas conviene prerenderizar y cuáles no.
  • Identificar el límite duro: qué información hace imposible hornear una página.

La bandera que el compilador lee

prerender es una exportación a nivel de módulo: una constante que declaras en el frontmatter de la página, fuera de cualquier función. Su valor decide si esa ruta se calcula en el build (true) o en la petición (false). Lo esencial es quién la lee y cuándo: la lee el compilador, en tiempo de build, antes de que exista petición alguna.

---
// src/pages/precios.astro
export const prerender = true;   // esta ruta se hornea, pase lo que pase con output

const planes = await cargarPlanes();
---
<h1>Precios</h1>

De ahí sale una regla que no conviene olvidar: el valor tiene que ser analizable estáticamente. Un literal true o false, no una expresión que dependa del entorno ni un cálculo en tiempo de ejecución. El compilador necesita conocer el momento de render antes de ejecutar nada, así que intentar decidir prerender a partir de una variable, una condición o una llamada no funciona: el momento de render no puede depender de datos que solo existen cuando la página ya corre. Es una restricción, sí, pero también una garantía —el modo de cada ruta queda fijado y es legible con solo mirar la primera línea del fichero.

Esa lectura estática tiene un beneficio que va más allá de tu comodidad. Como Astro sabe, antes de construir, exactamente qué rutas necesitan servidor y cuáles no, puede hornear las estáticas a ficheros y meter solo las dinámicas en el punto de entrada del adaptador. La bandera no es una sugerencia que se evalúa tarde: es un dato del build que decide qué código acaba en el servidor. Y funciona igual en los endpoints .ts, no solo en las páginas .astro.

// src/pages/manifiesto.json.ts  -> horneado aunque el sitio sea 'server'
export const prerender = true;

export const GET = () =>
  Response.json({ nombre: 'Mi sitio', version: 7 });

Un endpoint de datos que no cambia entre despliegues no tiene por qué costar una invocación de servidor en cada llamada: prerenderízalo y se convierte en un fichero servido por la CDN, aunque el resto del proyecto corra en modo server. La bandera decide el momento de cálculo de cualquier ruta —página o endpoint— con la misma regla.

Invertir el valor por defecto por ruta

La bandera cobra sentido cuando contradice al proyecto. Bajo output: 'static', el valor por defecto de cada página es prerender = true, así que escribir export const prerender = false es lo interesante: saca esa ruta concreta del horno y la vuelve bajo demanda. Bajo output: 'server' ocurre lo simétrico: el defecto es false, y escribir = true congela una ruta que, si no, se serviría viva.

---
// Proyecto estatico (output: static) con UNA ruta dinamica:
// src/pages/buscar.astro
export const prerender = false;

const q = Astro.url.searchParams.get('q') ?? '';
const resultados = await buscar(q);
---
<h1>Resultados para {q}</h1>

Piensa en la bandera como el lápiz con el que trazas una costura a través de tu propio sitio. A un lado quedan las páginas horneadas —el documento, lo que es igual para todos—; al otro, las servidas vivas —la aplicación, lo que depende de quién pregunta y cuándo—. Esa costura no la dibuja el framework: la dibujas tú, ruta por ruta, y su forma es una de las decisiones de arquitectura más consecuentes que tomarás, porque determina qué parte de tu sitio necesita un servidor y qué parte vive feliz como ficheros planos.

Conviene saber qué no cambia al accionar el interruptor. El middleware, si lo tienes, sigue corriendo para las rutas dinámicas; las colecciones de contenido se consultan igual con getCollection en uno y otro momento; y una ruta dinámica con parámetros deja de necesitar getStaticPaths, porque el servidor resuelve el segmento al vuelo en vez de enumerarlo en el build. La bandera cambia cuándo corre la página, no el vocabulario con que la escribes.

Cuándo prerenderizar y cuándo no

La pregunta operativa es directa: ¿esta página es la misma para todo el mundo y estable entre publicaciones? Si la respuesta es sí, prerenderízala; si es no, sírvela bajo demanda. De ahí salen dos listas casi mecánicas.

  • Hornéala cuando su contenido se conoce en el build, es idéntico para cada visitante, cambia con poca frecuencia y su conjunto de rutas es acotado: una portada, un artículo de blog, una página de documentación, una landing.
  • Sírvela viva cuando depende de la petición o del usuario, debe estar siempre fresca, o su universo de rutas es ilimitado: un buscador, un panel de cuenta, un carrito, un catálogo de millones de fichas.

Ese último criterio —el tamaño del universo de rutas— decide muchos casos por sí solo. Prerenderizar exige enumerar de antemano cada página que existirá, y hay conjuntos que no caben en esa foto: los resultados de una búsqueda son infinitos, las fichas de un catálogo gigantesco tardarían horas en hornearse, un perfil por usuario nace después del despliegue. Cuando el conjunto es abierto o desmesurado, el bajo demanda no es una preferencia sino la única vía practicable, porque el build no puede listar lo que no cabe o aún no existe.

💡
La duda se resuelve por el coste de equivocarse

Ante una página ambigua, inclínate por prerenderizar y comprueba si algo se rompe. El coste de un estático mal elegido es visible y benigno —datos algo viejos que un rebuild refresca—, mientras que el de un dinámico innecesario es silencioso y permanente: un servidor que se ejecuta y se paga en cada visita para producir siempre lo mismo. Cuando ambos parecen válidos, el estático es la apuesta segura, porque su modo de fallar no te cuesta dinero ni latencia.

El límite: lo que no se puede hornear

Muchas veces la elección no es tuya: la naturaleza de la página la decide. Una ruta que lee algo que solo existe en la petición —las cabeceras que envió el navegador, una cookie de sesión, la cadena de búsqueda de la URL, la dirección IP del visitante— no puede prerenderizarse de forma sensata, porque en el build no hay petición de la que leer esos datos.

---
// Esto NO puede ser estatico: depende de la peticion
export const prerender = false;   // obligatorio aqui

const token = Astro.cookies.get('sesion')?.value;
const idioma = Astro.request.headers.get('accept-language');
---

Si por descuido dejas una página así como estática, no obtienes un error claro sino algo peor: valores vacíos o congelados. La cookie sale indefinida, la cabecera nula, la búsqueda en blanco, y algunos accesos —como la IP del cliente— lanzan directamente. El síntoma es una página que “a veces no personaliza”, un fallo escurridizo cuyo origen es haber pedido a un fichero horneado que se comporte como un servidor vivo. Por eso el límite es útil: no es una molestia, es la guía que decide por ti muchos casos dudosos. Si la página necesita la petición, es bajo demanda, y no hay más que hablar.

El razonamiento inverso también ilumina. Si repasas una página y ninguna de sus líneas toca la petición —ni cookies, ni cabeceras, ni la consulta, ni la IP—, entonces nada impide hornearla, y probablemente deberías. La ausencia de accesos de petición es la firma de un documento; su presencia, la de una aplicación. Leer una página con esa pregunta en la cabeza —¿toca esto la petición?— convierte la decisión de prerender en algo casi mecánico.

En la práctica, ese hábito de lectura vuelve la costura casi automática: recorres tus páginas, señalas cuáles tocan la petición, y esas —y solo esas— llevan la bandera dinámica. El resto se queda en el valor por defecto estático, que es donde quieres que viva la mayoría. Así, prerender deja de ser una decisión que tomas página a página con esfuerzo y se vuelve la consecuencia de una única lectura atenta del código.

Un checklist rápido para decidir de un vistazo:

  • ¿Lee cookies, cabeceras, la consulta o la IP? Entonces dinámica, sin excepción.
  • ¿Su universo de rutas es ilimitado o crece sin control? Dinámica.
  • ¿Debe reflejar cambios al segundo, sin margen de retraso? Dinámica.
  • ¿Nada de lo anterior? Estática, y disfrutas de su velocidad y su baratura.
🧊

prerender true

Congela la ruta en el build. Para contenido igual para todos, estable y de universo acotado.

🔥

prerender false

Sirve la ruta viva en cada peticion. Para lo personalizado, lo fresco o lo ilimitado.

🔎

Analizable en build

El valor debe ser un literal true o false. El momento de render no puede depender de datos de ejecucion.

✂️

La costura

prerender traza a mano la frontera entre lo horneado y lo vivo a traves de tus rutas.

flowchart TD
P[una pagina concreta] --> Q{depende de la peticion o del usuario}
Q -->|si| ON[prerender false bajo demanda]
Q -->|no| Q2{cambia entre publicaciones}
Q2 -->|raramente| PRE[prerender true horneada]
Q2 -->|constantemente| ON
style P fill:#89b4fa,color:#11111b
style PRE fill:#a6e3a1,color:#11111b
style ON fill:#f9e2af,color:#11111b
⚠️
Un estático que lee la petición falla en silencio

El error más traicionero de este nivel no es un build roto, sino una página horneada que intenta leer datos de petición. No revienta el despliegue: revienta la lógica en producción, donde la cookie es indefinida y la personalización nunca ocurre. Como el build no se queja, el fallo llega a los usuarios. La defensa es un hábito: en cuanto una página toca Astro.request, Astro.cookies, Astro.clientAddress o Astro.url.searchParams, escribe export const prerender = false sin pensarlo, porque acabas de declarar, con esos accesos, que la página es una conversación y no un documento.

Dibujar el prerender es dibujar la frontera entre documento y aplicación

La bandera prerender parece un detalle de configuración, pero es en realidad la herramienta con la que ejerces una de las decisiones de arquitectura más profundas de la web: dónde termina el documento y empieza la aplicación. Toda la historia del desarrollo web es un péndulo entre esos dos polos. Los primeros sitios eran documentos: ficheros servidos igual a todos, rápidos, cacheables, sin estado. Luego llegó la aplicación: todo dinámico, todo calculado por petición, personalizado y fresco pero caro y frágil. Cada época eligió un polo para el sitio entero, y esa elección total era el error, porque casi ningún sitio real es puro documento o pura aplicación. Un periódico es documento en sus artículos y aplicación en su buscador. Una tienda es documento en sus fichas de catálogo y aplicación en su carrito. La frontera entre ambos no corre entre proyectos, sino dentro de cada uno, serpenteando entre sus páginas. Lo que prerender te da es el lápiz para trazar esa frontera exactamente donde cae en tu caso, ruta por ruta, en lugar de aceptar una respuesta global impuesta por el framework. Y trazarla bien es un arte con consecuencias medibles: cada página que consigues dejar del lado estático es una que se sirve instantánea, resiste una avalancha de tráfico sin inmutarse y no cuesta nada operar; cada una que necesitas del lado dinámico es una que paga cómputo por visita a cambio de frescura o personalización que no podrías falsear. El desarrollador que entiende esto no pregunta “¿mi sitio es estático o dinámico?” —una pregunta mal planteada— sino “¿por dónde pasa, en mi sitio, la línea entre lo que puede congelarse y lo que debe vivir?”. Dominar prerender es aprender a ver esa línea, y a ponerla donde de verdad está.

⚔️ Traza la costura de tu sitio
  1. En un proyecto output: 'static', crea src/pages/buscar.astro, léela con Astro.url.searchParams y márcala con export const prerender = false; comprueba que el build ahora emite un servidor.
  2. Intenta poner export const prerender a un valor calculado en vez de un literal y observa el error del compilador; razona por qué el momento de render no puede decidirse en ejecución.
  3. Deja por error una página que lee Astro.cookies como estática, despliégala y verifica que la cookie sale indefinida sin romper el build.
  4. Haz un inventario de las páginas de un sitio real y clasifica cada una como horneable o viva; el reparto que obtengas es la costura de su arquitectura.