Saltar al contenido

@astrojs/node: standalone y middleware

El adapter para servir Astro en un servidor Node que tú controlas. Los dos modos que ofrece: standalone, que construye un servidor completo listo para arrancar con node, y middleware, que produce un handler que injertas en un servidor Express o Fastify propio. Configurar HOST y PORT, empaquetar en un contenedor, inyectar locals, y situar el proceso detrás de un proxy inverso y varios núcleos.

standalone: un servidor completo listo para correr

El modo standalone es el más directo: le pides a Astro que construya un servidor web entero, con su bucle de escucha ya incluido. El build deja un punto de entrada ejecutable, y arrancarlo es una sola orden.

JS
import { defineConfig } from 'astro/config';import node from '@astrojs/node';export default defineConfig({  output: 'server',  adapter: node({ mode: 'standalone' }),});
Shell
npm run build# el build produce dist/server/entry.mjsnode ./dist/server/entry.mjs# el servidor escucha y sirve estaticos mas rutas dinamicas

Ese servidor no depende de nada externo: sirve por sí mismo los assets del cliente y ejecuta las rutas bajo demanda. Su comportamiento se ajusta por variables de entorno —HOST para la interfaz de escucha y PORT para el puerto (8080 por defecto)—, de modo que el mismo artefacto se despliega en distintos entornos sin recompilar. Esa portabilidad lo hace el candidato natural para un contenedor.

DOCKERFILE
# Dockerfile para el modo standaloneFROM node:lts-alpineWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run buildENV HOST=0.0.0.0ENV PORT=8080EXPOSE 8080CMD ["node", "./dist/server/entry.mjs"]

middleware: Astro como una pieza de tu servidor

A veces no quieres que Astro sea el servidor, sino que viva dentro de uno tuyo —porque ya tienes rutas de API, autenticación, sockets o lógica que no es de Astro—. Para eso está el modo middleware: el build no arranca nada, sino que exporta un handler que tú montas donde quieras.

JS
import { defineConfig } from 'astro/config';import node from '@astrojs/node';export default defineConfig({  output: 'server',  adapter: node({ mode: 'middleware' }),});
JS
// servidor propio con Expressimport express from 'express';import { handler as ssrHandler } from './dist/server/entry.mjs';const app = express();app.use(express.static('dist/client/')); // sirve tu los estaticosapp.use(ssrHandler);                     // y delega el resto en Astroapp.listen(8080);

Aquí tú posees la cadena: registras tus rutas antes o después de Astro, decides quién sirve los estáticos y cómo se ordenan las capas. El handler acepta incluso un cuarto argumento para inyectar locals, de forma que tu servidor puede pasar datos —un usuario autenticado, un identificador de petición— al contexto que verán tus páginas.

JS
app.use((req, res, next) => {  const locals = { usuario: req.session?.usuario };  ssrHandler(req, res, next, locals); // los locals llegan a Astro.locals});

Detrás de un proxy inverso y varios núcleos

Un proceso Node en producción rara vez se expone desnudo a internet. Lo habitual es ponerle delante un proxy inverso —nginx, Caddy— que termina el TLS, comprime respuestas, cachea estáticos y reparte carga. El proceso de Astro escucha en un puerto local, y el proxy es la cara pública que atiende el mundo.

Node ejecuta un único hilo por proceso, así que para aprovechar varios núcleos se lanzan varias instancias —con un gestor como PM2, con systemd, o con el cluster del propio Node— y el proxy balancea entre ellas. Ese patrón, un servidor de aplicación replicado tras un balanceador, es el mismo que rige cualquier backend Node clásico, y hereda sus mismas responsabilidades: vigilar la salud de cada proceso, reiniciarlo si cae y drenar conexiones al desplegar una versión nueva.