wandres.dev
DEPLOY · a producción

Deploy SSR y edge: Workers, Vercel, Node y Docker

Publicar un sitio con renderizado bajo demanda en dos familias de runtime: el edge de Cloudflare Workers, Vercel o Netlify Functions, y un servidor Node de larga vida en un VPS o contenedor Docker. Qué adapter elige cada destino, en qué se diferencian el edge y el servidor persistente, y cómo empaquetar el handler con Docker.

⏱ 17 min

El estático se despliega copiando una carpeta; el renderizado bajo demanda, no. Una ruta que se resuelve en el instante de cada petición necesita un proceso vivo que ejecute tu código, y ese proceso puede vivir en lugares muy distintos: en el edge —funciones efímeras que arrancan al vuelo en cientos de puntos de presencia, como Cloudflare Workers, Vercel o Netlify Functions— o en un servidor Node de larga vida que tú controlas, en un VPS o dentro de un contenedor Docker. El adapter que elijas fija ese destino, y con él heredas un modelo de ejecución con reglas propias: cuánto duran las peticiones, qué APIs tienes, cuánto estado puedes guardar. Elegir dónde corre tu SSR es elegir la física de tu aplicación.

🎯 Al terminar esta lección sabrás
  • Distinguir las dos familias de runtime: edge efímero y servidor de larga vida.
  • Elegir el adapter adecuado para Workers, Vercel, Netlify o Node.
  • Entender los límites del edge —tiempo, APIs, estado— frente a Node.
  • Empaquetar la salida de servidor en una imagen Docker reproducible.

Dos familias de runtime para el mismo handler

Tu código de servidor no cambia según dónde lo despliegues —sigue leyendo Astro.request y devolviendo un Response—, pero el entorno que lo ejecuta sí, y se divide en dos familias con caracteres opuestos. El adapter es lo que traduce entre tu handler universal y la familia concreta que elijas.

☁️

Cloudflare Workers

Funciones en el edge sobre APIs web. Arranque casi instantáneo, cercanía al usuario y acceso a KV, D1 y R2.

Vercel

Funciones serverless o de edge, con renderizado incremental e imágenes de plataforma. Despliegue integrado con el repositorio.

🔷

Netlify Functions

Funciones y edge functions con la CDN de imágenes de Netlify y previews por rama listos de fábrica.

🖥️

Node en VPS o Docker

Un servidor persistente que tú posees. Control total del runtime, del sistema de ficheros y del ciclo de vida del proceso.

La primera familia es efímera y distribuida: la función no existe hasta que llega una petición, se ejecuta cerca del usuario y muere enseguida. La segunda es persistente y centralizada: un proceso Node que arrancas una vez y que atiende peticiones hasta que lo pares. Esa diferencia de vida es la raíz de todo lo demás.

De esa raíz brotan cuatro consecuencias que conviene tener presentes al elegir:

  • Arranque: casi instantáneo en el edge; el servidor Node ya está caliente, pero hay que mantenerlo vivo.
  • Estado: el edge no recuerda nada entre peticiones; Node puede guardar caché o conexiones en memoria.
  • Escalado: el edge se multiplica solo por petición; el servidor lo escalas tú, con más instancias o réplicas.
  • APIs: el edge ofrece un subconjunto web; Node te da la plataforma completa y cualquier módulo nativo.

Edge: efímero, cercano y con límites

Instalar un adapter de edge es una línea, y el build cambia su salida para producir funciones en lugar de un servidor completo.

npx astro add cloudflare
# o vercel, o netlify: cada uno produce el formato de su plataforma

El edge compra dos ventajas enormes: latencia —tu código corre en el punto de presencia más cercano al visitante, no en un centro de datos lejano— y escalado sin esfuerzo —cada petición arranca su propia instancia, así que mil peticiones simultáneas son mil funciones, sin que tú provisiones nada—. A cambio, impone una física estricta que se resume en tres límites:

  • Sin Node completo: el runtime son APIs web, así que módulos nativos y librerías que dependan de binarios de Node no cargan.
  • Techo de CPU: cada invocación tiene un tiempo máximo, de modo que un cómputo largo o un procesado pesado no encaja.
  • Sin memoria persistente: la función no recuerda nada entre peticiones; el estado vive fuera, en KV, en una base de datos o en un almacén externo.

Ninguno de esos límites es un defecto: son el precio exacto de la ubicuidad. Una función que no guarda estado y termina rápido es justo la que puede replicarse en cientos de sitios sin coordinación.

⚠️
El edge no es Node: escribe contra la plataforma web

El error más frecuente al mudarse al edge es asumir que se dispone de todo Node. No es así. No hay fs para tocar el disco, no hay APIs nativas del sistema, y librerías que dependan de binarios de Node fallarán al desplegar aunque funcionaran en local. La disciplina que salva es la misma que predica Astro en su núcleo: programa contra fetch, Request, Response, URL y crypto de la plataforma web, y deja fuera todo lo que huela a específico de Node. Si tu handler se ciñe a esos estándares, el mismo código corre igual en Workers que en Vercel edge que en un servidor Node.

Node en un VPS o contenedor

La otra familia es el servidor Node de siempre: un proceso de larga vida que arranca, escucha en un puerto y atiende peticiones hasta que lo detienes. Es el destino que da control total —Node completo, disco, estado en memoria, cualquier librería— a cambio de que tú te hagas cargo de mantenerlo vivo.

// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  adapter: node({ mode: 'standalone' }),
});

En modo standalone, el build produce un servidor autónomo que arrancas con un solo comando. Eso lo hace ideal para meterlo en un contenedor: una imagen Docker que empaqueta Node, tus node_modules y tu dist/, y que cualquier orquestador puede correr igual en tu portátil que en producción.

# Etapa de build: instala todo y construye
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Etapa final: solo lo necesario para ejecutar
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD ["node", "./dist/server/entry.mjs"]

El patrón de dos etapas —multi-stage— es lo que separa una imagen profesional de una pesada: la primera etapa tiene todo lo necesario para construir, pero la imagen final solo copia el dist/ y los node_modules, dejando fuera el código fuente y las herramientas de build. El resultado es una imagen pequeña, reproducible y arrancable en cualquier sitio que hable Docker: un VPS con un docker run, un Kubernetes, un servicio de contenedores gestionado.

El precio de todo ese control es la responsabilidad. Un servidor persistente que tú posees también eres tú quien lo mantiene arriba: vigilar que el proceso no muera, reiniciarlo si cae, parchear el sistema, escalar cuando el tráfico crece. El edge te quita ese trabajo a cambio de sus límites; el servidor te da libertad a cambio de operarlo. Elegir entre ambos no es elegir el mejor, sino decidir qué prefieres tener en tus manos: la comodidad de no mantener nada o el control de mantenerlo todo.

📝
El mismo código, listo para las dos físicas

Lo notable es que la elección entre edge y servidor no te obliga a bifurcar el código. Como tu handler habla el lenguaje de la plataforma web —Request, Response, fetch—, el mismo fichero de página puede desplegarse en un Worker o en un contenedor Node sin tocar una línea, siempre que no dependas de APIs exclusivas de Node. Eso convierte el destino en una decisión reversible: si el edge se te queda corto, migras a Node cambiando el adapter, y al revés. La portabilidad no es una promesa de marketing, es la consecuencia de programar contra el estándar.

💡
El contenedor es la unidad de despliegue portátil

La virtud de empaquetar el servidor Node en Docker no es solo el aislamiento: es que la imagen es la misma en todas partes. La que arrancas en tu máquina, la que corre CI y la que sirve producción son byte a byte idénticas, así que el clásico funciona en mi máquina deja de tener sentido. Fijar la versión de Node en el FROM, instalar con npm ci y copiar solo lo imprescindible convierte tu despliegue en un artefacto que no depende del entorno donde aterrice. Es la portabilidad del estático, aplicada a un servidor vivo.

flowchart TD
REQ[peticion del usuario] --> ADP[adapter del destino]
ADP --> EDGE[edge Workers Vercel Netlify]
ADP --> NODE[Node en VPS o contenedor]
EDGE --> EFI[funcion efimera cerca del usuario]
NODE --> PER[proceso persistente que controlas]
EFI --> RESP[Response al cliente]
PER --> RESP
style ADP fill:#89b4fa,color:#11111b
style RESP fill:#a6e3a1,color:#11111b
Elegir un runtime es elegir una física, no un proveedor

Es fácil vivir la elección de destino como una decisión comercial —qué plataforma tiene mejor precio, mejor panel, mejor marketing—. Pero por debajo de esa capa hay una decisión mucho más honda y más duradera: qué física rige la ejecución de tu código. El edge y el servidor persistente no son dos precios del mismo producto, son dos universos con leyes distintas. En el edge el tiempo es escaso y la memoria no persiste, pero tu código nace al lado del usuario y se multiplica sin que muevas un dedo; en un servidor Node el tiempo y la memoria son tuyos, guardas estado y usas cualquier librería, pero a cambio hay un proceso que mantener vivo, que escalar a mano y que puede caerse. Ninguna de las dos es mejor: son adecuadas para cosas distintas. Un handler que consulta un dato y responde en milisegundos florece en el edge; uno que sostiene una conexión larga, procesa un fichero pesado o guarda estado en memoria pide un servidor persistente. Y aquí está la lección que Astro convierte en concreta: como su motor está escrito contra la plataforma web, esa elección de física se toma tarde y en un solo sitio —una línea de adapter—, en lugar de impregnar cada fichero desde el primer día. Programas contra el estándar, y decides el universo al final. Esa es la diferencia entre acoplarte a un proveedor y elegir un runtime: en el primer caso el destino te posee; en el segundo, tú lo eliges, y puedes cambiarlo el día que la física de tu problema cambie.

⚔️ Despliega el mismo handler en dos físicas
  1. Añade un adapter de edge con astro add y despliega una ruta bajo demanda; mide su latencia desde tu ubicación.
  2. Cambia al adapter de Node en modo standalone y arranca el servidor en local con el comando que genera el build.
  3. Escribe un Dockerfile multi-stage, construye la imagen y córrela con docker run, comprobando que responde en el puerto.
  4. Introduce una llamada a fs de Node en tu handler y observa qué destino la acepta y cuál la rechaza al desplegar.