Qué es un adapter y por qué SSR lo necesita
La pieza que falta cuando un sitio deja de ser estático: el adapter como interfaz entre el motor de Astro y un runtime concreto. Por qué el modo estático no lo necesita y el bajo demanda sí, cómo el par Request/Response de la plataforma web es el contrato compartido, cómo se instala y registra con astro add, y cómo estático y dinámico conviven en un mismo build.
El modo estático de Astro no necesita servidor: el build emite HTML, CSS y JavaScript, ficheros planos que cualquier CDN sirve sin ejecutar una línea de tu código. Pero en cuanto una sola ruta se renderiza bajo demanda, alguien tiene que correr tu código en el instante de cada petición —y Astro, por sí solo, no sabe hablar con Vercel, con Cloudflare ni con tu servidor Node—. El adapter es esa pieza de traducción: enseña a Astro a construir para un runtime concreto y a convertir la petición nativa de esa plataforma en el Request estándar que su motor entiende. Sin adapter no hay bajo demanda; con él, el mismo proyecto aterriza en destinos radicalmente distintos.
- Entender por qué el modo estático no necesita adapter y el bajo demanda sí.
- Ver el adapter como la interfaz entre el motor de Astro y un runtime concreto.
- Reconocer el par
Request/Responsede la plataforma web como el contrato compartido. - Instalar y registrar un adapter con
astro add, y ver qué produce el build.
Sin servidor no hay quién ejecute tu código
Un sitio estático es un montón de ficheros terminados. El build los cuece una vez, y a partir de ahí servirlos es un acto mecánico: un CDN o un cubo de almacenamiento devuelve bytes que ya existían, sin computar nada. Por eso el modo static no pide adapter —no hay código tuyo corriendo en producción, solo artefactos horneados—.
El renderizado bajo demanda rompe esa foto. Una ruta con export const prerender = false no se resuelve en el build: se resuelve cuando llega la petición, leyendo Astro.params, consultando datos frescos, decidiendo la respuesta al vuelo. Eso exige un proceso vivo que ejecute tu código en cada visita. Astro sabe producir ese código —su build genera un manejador de peticiones—, pero no sabe, por sí mismo, cómo la plataforma de destino lo arranca ni con qué forma le entrega cada petición. Ese saber es justo lo que aporta el adapter.
Si marcas una ruta como bajo demanda y ejecutas el build sin un adapter instalado, Astro se detiene con un error explícito: no puede construir para un destino que no conoce. No es un fallo tuyo de código, es una pieza que falta en la configuración. El adapter es el requisito estructural del SSR, no un extra opcional que se pueda posponer.
El adapter: un traductor entre dos formas de petición
El motor de Astro está escrito contra la plataforma web. Su corazón es una aplicación que recibe un Request estándar y devuelve un Response estándar —los mismos objetos que existen en cualquier navegador—. Esa firma es universal y no menciona a ningún proveedor.
// La idea del contrato interno, no codigo que escribas a diario
const app = new App(manifest);
const response = await app.render(request); // Request -> Response
El problema es que ningún runtime real habla exactamente ese dialecto. Node entrega un IncomingMessage y espera que escribas en un ServerResponse. Una función serverless recibe un objeto de evento con su propia forma. Un Worker de Cloudflare ya trae una petición casi estándar, pero con extras propios. El adapter es el traductor bidireccional: toma la petición nativa de la plataforma, la convierte en el Request que el motor espera, y transforma el Response de vuelta en lo que el runtime sabe emitir.
Esa es la razón por la que nunca escribes manejo de peticiones específico de una plataforma dentro de tus páginas. No parseas cabeceras al estilo de Node ni construyes la salida con la API de un proveedor: lees Astro.request, devuelves un Response, y confías en que el adapter haga la conversión en ambos sentidos. Toda la especificidad del runtime empieza y termina en esa capa delgada.
Técnicamente, un adapter es una integración de Astro con un rol reservado: además de engancharse a los hooks del ciclo de vida, declara el runtime de destino, el formato de salida del build y qué características de servidor soporta. Por eso no vive en el array integrations, sino en la clave adapter de la configuración: solo puede haber uno, porque un build apunta a un único destino a la vez.
Instalar y registrar un adapter
La forma canónica de añadir un adapter es astro add, que instala el paquete, lo registra en astro.config.mjs y ajusta lo que haga falta sin que edites nada a mano.
# Elige el destino: node, vercel, netlify o cloudflare
npx astro add node
# instala @astrojs/node, importa el paquete y rellena la clave adapter
El resultado en la configuración es una sola línea nueva, pero es la que cambia todo el destino del proyecto:
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
adapter: node({ mode: 'standalone' }),
});
Con el adapter presente, las rutas bajo demanda ya tienen dónde ejecutarse. Fíjate en lo que no ha cambiado: ni una sola de tus páginas. El acoplamiento a la plataforma cabe entero en esa importación y esa clave; el resto de tu código sigue hablando el lenguaje universal del motor. Astro mantiene un puñado de adapters oficiales, uno por familia de destino.
@astrojs/node
Un servidor Node que tú controlas, en modo standalone o como middleware de tu propio Express.
@astrojs/vercel
Funciones serverless o de edge en Vercel, con renderizado incremental e imágenes de la plataforma.
@astrojs/netlify
Funciones y edge functions de Netlify, con su CDN de imágenes y despliegue integrado.
@astrojs/cloudflare
Workers en el edge sobre APIs web, con acceso a KV, D1, R2 y geolocalización.
Estático y dinámico conviven en el mismo build
Con un adapter presente no estás obligado a volver dinámico todo el sitio. El output puede seguir siendo static y tú marcas bajo demanda solo las rutas que lo necesiten con export const prerender = false. El build entiende esa mezcla y produce dos artefactos complementarios.
dist/
client/ # HTML prerenderizado y assets, servibles por un CDN
server/ # el handler que ejecuta las rutas bajo demanda
La parte de client son ficheros terminados que la plataforma sirve sin computar nada; la de server es el código que el adapter conecta al runtime para las rutas vivas. El adapter no solo traduce peticiones: también organiza este reparto, indicándole a la plataforma qué servir como estático y qué enviar a tu manejador.
---
// src/pages/blog/[slug].astro -> estatico por defecto
// src/pages/carrito.astro -> bajo demanda
export const prerender = false;
---
El mismo mecanismo funciona al revés. Si configuras output: 'server', todo se vuelve bajo demanda por defecto y marcas con export const prerender = true las páginas que sí quieras hornear —la portada, el aviso legal—. Estático y servidor no son dos proyectos distintos, sino dos posiciones por defecto del mismo dial, y el adapter sostiene cualquiera de ellas sin que cambie nada más en tu código.
Instalar un adapter no convierte tu sitio en una aplicación de servidor: solo abre la posibilidad. Un blog puede seguir siendo casi todo estático —rápido, cacheable, barato— y reservar el renderizado bajo demanda para la búsqueda, el carrito o un panel. Esa mezcla es la norma, no la excepción, y el adapter la sostiene sin obligarte a elegir un extremo para todo el proyecto.
Cuando ejecutas astro add, el comando no solo baja el paquete: detecta lo que tu proyecto necesita, escribe la importación, rellena la clave adapter y, si hace falta, ajusta el output. Editar todo eso a mano es posible, pero astro add lo hace sin errores de sintaxis y deja la configuración en un estado coherente desde el primer momento.
flowchart LR PLAT[peticion nativa de la plataforma] --> AD[adapter traduce de entrada] AD --> REQ[Request web estandar] REQ --> APP[motor App de Astro] APP --> RES[Response web estandar] RES --> AD2[adapter traduce de salida] AD2 --> OUT[respuesta nativa de la plataforma] style APP fill:#89b4fa,color:#11111b style RES fill:#a6e3a1,color:#11111b
Que baste cambiar una línea para mudar tu sitio de tu portátil a la red global de un proveedor no es magia ni casualidad: es la consecuencia visible de una decisión de arquitectura tomada mucho antes, en el corazón del motor de Astro. Ese motor no está escrito contra Node, ni contra ninguna plataforma; está escrito contra la plataforma web —los mismos objetos Request, Response, URL, Headers y fetch que existen en cualquier navegador—. Una página de Astro, en el fondo, es una función que recibe un Request y devuelve un Response, y esa firma no nombra a ningún proveedor. El adapter es el fino traductor que conecta esa función universal con el dialecto concreto de un runtime: convierte el IncomingMessage de Node, o el evento de una función serverless, o la petición de un Worker, en el Request estándar que el motor espera, y hace el viaje de vuelta con el Response. Toda la lógica de tu sitio vive del lado universal; toda la dependencia del proveedor vive comprimida en el adapter, una capa delgada y reemplazable que no toca tu código. Esta es una lección de diseño que trasciende Astro: cuando programas contra un estándar y aíslas lo específico del proveedor en una frontera nombrada, compras portabilidad de verdad —no la portabilidad prometida de un framework que te encierra, sino la que se demuestra el día que cambias node por cloudflare y todo sigue funcionando—. El adapter no es un detalle de despliegue: es la prueba de que el acoplamiento a la plataforma puede ser una elección tardía y localizada, en lugar de una condena que impregna cada fichero.
- En un proyecto estático, marca una ruta con
export const prerender = falsey ejecuta el build: lee con calma el error que reclama un adapter. - Instala uno con
npx astro add nodey observa exactamente qué línea aparece enastro.config.mjs. - Vuelve a construir, localiza en
distlas carpetasclientyserver, y razona qué papel cumple cada una. - Explica con tus palabras por qué el mismo motor de Astro puede servir tanto en Node como en el edge sin reescribir tus páginas.