El objeto Astro en SSR
Bajo demanda, cada página recibe algo que en estático no existe de verdad: la petición. El objeto global `Astro` se puebla con la realidad del instante —`Astro.request`, `Astro.url`, cookies, cabeceras y `Astro.clientAddress`—. Cómo leer la petición entrante, gestionar cookies y la respuesta, y qué miembros solo tienen sentido con un servidor vivo.
Cuando una página se sirve bajo demanda, recibe algo que en estático no existía más que como cascarón: la petición. El objeto global Astro, que en el build era una foto fija, se puebla en tiempo de servidor con la realidad del instante —la URL exacta que se pidió con su cadena de búsqueda, las cabeceras que envió el navegador, las cookies que trae, la dirección desde la que llega—. Aprender SSR en Astro es, en gran parte, aprender a leer ese objeto: la superficie por la que tu página escucha a quien la visita y le responde a medida.
- Leer la petición entrante con
Astro.requestyAstro.url. - Gestionar cookies con
Astro.cookiesy cabeceras conAstro.request.headers. - Obtener la dirección del visitante con
Astro.clientAddressy ajustar la salida conAstro.response. - Saber qué miembros de
Astrosolo cobran sentido bajo demanda.
La petición como objeto: Astro.request y Astro.url
En el corazón del renderizado bajo demanda hay un objeto estándar de la plataforma web: Astro.request es un Request, el mismo que usan los Service Workers, Deno y los runtimes del edge. No es una invención de Astro, y eso importa: lo que aprendes aquí es transferible. De él sacas el método, el cuerpo y, sobre todo, las cabeceras que el cliente mandó.
Junto a él, Astro.url es un objeto URL ya parseado con la dirección concreta que se pidió. Su miembro más jugoso es searchParams, la cadena de búsqueda: el trozo de la URL que solo existe petición a petición, porque cada visitante escribe la suya.
---
export const prerender = false;
const pagina = Number(Astro.url.searchParams.get('pagina') ?? '1');
const metodo = Astro.request.method;
const acepta = Astro.request.headers.get('accept-language');
---
<p>Sirviendo la pagina {pagina} para un cliente que prefiere {acepta}</p>
Detente en searchParams: en una página horneada estaría siempre vacío, porque en el build no hay una consulta concreta que leer. Bajo demanda, en cambio, contiene lo que el visitante puso tras el interrogante, y con ello construyes búsquedas, filtros, paginaciones y cualquier vista que dependa de lo que pide cada uno. Es la puerta más común por la que una página deja de ser un documento y se vuelve receptiva.
El método y el cuerpo abren la otra mitad del abanico. Una página o un endpoint bajo demanda pueden mirar Astro.request.method para distinguir una lectura de una escritura, y leer el cuerpo enviado con Astro.request.formData o con el parseo de JSON de la propia plataforma. Ahí es donde una URL deja de solo mostrar y empieza a recibir: un formulario que se procesa, un dato que se guarda.
---
export const prerender = false;
if (Astro.request.method === 'POST') {
const datos = await Astro.request.formData();
const correo = datos.get('correo');
await suscribir(correo);
}
---
<form method="POST"><input name="correo" type="email" /></form>
Astro tiene además las actions para esto con tipos y validación, pero por debajo todo se apoya en el mismo Request estándar que estás leyendo aquí. Reconocerlo te da una brújula: cuando dudes de qué API usar para leer la petición, la respuesta casi siempre es “la de la plataforma web”, porque es sobre ella que Astro construye.
Cookies, cabeceras y la dirección del cliente
Las cookies tienen su propia API tipada en Astro.cookies, con métodos para leer, escribir, comprobar y borrar. Leer una cookie te da el estado que el navegador arrastra entre visitas —una sesión, una preferencia—; escribirla, junto con ajustar la respuesta saliente, es como devuelves ese estado actualizado.
---
export const prerender = false;
const sesion = Astro.cookies.get('sesion')?.value;
const usuario = sesion ? await validarSesion(sesion) : null;
if (!usuario) {
Astro.response.status = 401;
}
Astro.cookies.set('ultima_visita', new Date().toISOString(), {
path: '/',
httpOnly: true,
secure: true,
sameSite: 'lax',
});
---
Fíjate en las opciones al escribir la cookie: no son adorno. httpOnly la oculta a JavaScript del cliente y frena el robo por scripts inyectados; secure la limita a HTTPS; sameSite acota cuándo se envía entre sitios y mitiga el CSRF. Una cookie de sesión sin estas banderas es un agujero de seguridad, y como se fijan en el servidor, solo existen de verdad bajo demanda. Aquí aparecen además dos miembros más del objeto: Astro.response te deja fijar el estado y las cabeceras salientes —un 401 para el no autenticado, una redirección, una cabecera de caché—, y Astro.clientAddress te entrega la dirección IP desde la que llega la petición, útil para geolocalización aproximada, límites de tasa o registro.
Lo que solo existe bajo demanda
El objeto Astro es siempre el mismo símbolo, pero no todos sus miembros están habitados en los dos momentos de render. Los que describen la petición solo cobran sentido cuando hay una. Estos son los que solo cobran vida bajo demanda:
Astro.url.searchParams, con lo que el visitante escribe tras el interrogante.Astro.request.headersyAstro.request.method, lo que envió el navegador.Astro.cookies, el estado que el cliente arrastra entre visitas.Astro.clientAddressyAstro.locals, la IP y lo que dejó el middleware.
En una página prerenderizada, Astro.request.headers viene esencialmente vacío, Astro.url.searchParams no contiene nada, las cookies no se pueden leer con fiabilidad, y Astro.clientAddress no se limita a devolver vacío: lanza un error, porque no existe ninguna dirección de cliente en el build.
De todos los miembros de petición, Astro.clientAddress es el más honesto en su fallo: en lugar de devolver un valor engañoso, lanza si la página está prerenderizada. Es la señal más clara de que estás pidiendo a un documento horneado que se comporte como un servidor. La cura es siempre la misma: si tu página lee la dirección del cliente —o cualquier otro dato de la petición— márcala con export const prerender = false. Ese acceso es, en el fondo, una declaración de que la página necesita estar viva.
Hay una consecuencia menos obvia de leer la petición: la respuesta deja de poder compartirse a ciegas. Una página horneada es idéntica para todos y una CDN puede repartirla sin más; una página que depende de una cookie o de una cabecera produce una respuesta distinta por visitante, y cachearla sin cuidado serviría los datos de uno a otro. Por eso, en cuanto tu página se personaliza, la caché deja de ser automática y pasa a exigir cabeceras deliberadas —o directamente ninguna caché compartida—. El objeto Astro poblado es cómodo, pero cada miembro de petición que lees ata la respuesta a un visitante concreto, y esa atadura es justo lo que hay que respetar al decidir qué se guarda y qué no.
Hay un miembro más que solo vive bajo demanda: Astro.locals, un objeto por petición que el middleware rellena antes de que la página se ejecute. Es el sitio canónico para pasar el usuario ya validado, el idioma resuelto o la conexión a datos desde el middleware hasta la página, sin repetir el trabajo en cada ruta. Como nace y muere con la petición, no existe en el build: es otra pieza del objeto Astro que solo tiene sentido cuando hay alguien preguntando, y su presencia es una señal más de que la página vive bajo demanda.
Astro.request
Un objeto Request estandar de la web. Metodo, cuerpo y cabeceras que envio el cliente.
Astro.url
Un objeto URL parseado. Su searchParams trae la cadena de busqueda de cada peticion.
Astro.cookies
API tipada para leer, escribir, comprobar y borrar cookies con httpOnly, secure y sameSite.
Astro.clientAddress
La direccion IP del visitante. Solo bajo demanda: lanza en una pagina horneada.
flowchart LR CLI[el navegador pide una URL] --> SRV[el servidor ejecuta la pagina] SRV --> OBJ[el objeto Astro se puebla] OBJ --> REQ[Astro request cabeceras y cuerpo] OBJ --> URL[Astro url y searchParams] OBJ --> CK[Astro cookies estado] OBJ --> IP[Astro clientAddress] style CLI fill:#89b4fa,color:#11111b style OBJ fill:#a6e3a1,color:#11111b style IP fill:#f9e2af,color:#11111b
Esa asimetría entre los dos momentos no es un defecto, sino la definición misma de lo que significa cada modo. El estático es una página escrita antes de conocer a su lector; el bajo demanda es una página escrita en el acto para el lector que acaba de llegar. El objeto Astro refleja esa diferencia con precisión: sus miembros de petición están vivos exactamente cuando hay alguien preguntando, y en blanco cuando la página se compuso en la soledad del build.
Este es también el motivo por el que no puedes “probar” SSR con solo leer la petición en una página estática y esperar que funcione: el objeto está ahí, pero sus miembros de petición no tienen nada que reflejar hasta que la página corre bajo demanda. La forma correcta de despertar el objeto Astro es declarar la página dinámica con export const prerender = false; entonces, y solo entonces, la petición existe y sus miembros se llenan.
Repara en que Astro.request es un Request, Astro.url es un URL y la respuesta se modela con un Response. Astro no inventa una API paralela: expone los objetos que ya define la web. La consecuencia es doble. Primero, lo que dominas aquí te sirve fuera de Astro, en cualquier runtime moderno. Segundo, el framework se vuelve más pequeño y honesto, porque delega en la plataforma en vez de reimplementarla. Leer la petición en Astro es leer la web tal como es.
La diferencia entre una página estática y una servida bajo demanda no es de rendimiento ni de despliegue: es ontológica, una diferencia en el modo de existir de la página. Una página estática es un monólogo. Se compone una vez, en la soledad del build, dirigida a un lector abstracto que aún no existe, y de ahí en adelante repite su discurso idéntico a cuantos lleguen, sin escuchar a ninguno. No tiene forma de saber quién la lee, desde dónde, con qué preferencias o con qué historia, porque cuando se escribió no había nadie. El objeto Astro, en ese mundo, es casi un cascarón: sus miembros de petición están en blanco porque no hay petición que reflejar. Una página bajo demanda es una conversación. No existe hasta que alguien la solicita, y cuando lo hace, nace particularizada para ese instante y ese visitante: lee la URL que pidió, las cabeceras que trae, las cookies que arrastra, la dirección desde la que llega, y con todo ello compone una respuesta que quizá nadie más recibirá nunca igual. El objeto Astro aquí está habitado, vivo, poblado con la realidad contingente de un otro concreto. Por eso Astro.clientAddress lanza en estático en lugar de mentir: no hay dirección que devolver porque no hay nadie con quien conversar, y el framework prefiere la verdad de un error a la ficción de un valor vacío. Entender SSR a fondo es entender que has cambiado el determinismo del documento —igual para todos, calculable de antemano, cacheable para siempre— por la contingencia del diálogo —distinta para cada uno, incalculable hasta que ocurre, atada al momento—. Ganas la capacidad de responder a quien pregunta; pagas con la pérdida de la certeza de saber, antes de la primera visita, qué dirás. El objeto Astro es la frontera exacta entre esos dos modos de ser, y sus miembros de petición son la parte de tu página que solo despierta cuando hay alguien al otro lado.
- Crea una página bajo demanda que lea
Astro.url.searchParamsy muestre un valor distinto según la consulta; verifica que cambia al cambiar la URL. - Lee una cabecera con
Astro.request.headers.get('accept-language')y adapta el saludo al idioma preferido del navegador. - Escribe una cookie con
Astro.cookiesusandohttpOnlyysameSite, ajustaAstro.response.statusa401cuando falte, y observa el estado en las herramientas del navegador. - Añade
Astro.clientAddress, despliégala como estática a propósito y comprueba que lanza; luego márcalaprerender = falsey confirma que ya funciona.