Cuando trabajamos con aplicaciones JavaScript en .NET Aspire, uno de los puntos más confusos es entender cómo se despliegan en producción. Durante el desarrollo local, AddViteApp levanta el servidor de Vite con HMR y todo funciona de maravilla. El problema viene cuando hay que publicar: ¿quién sirve los archivos en producción?
Este artículo explica los tres modelos de hosting que ofrece Aspire para frontends JavaScript y cuándo usar cada uno.
El malentendido más común
AddViteApp no es un servidor web de producción. Es un recurso de build. En modo desarrollo sirve el dev server de Vite; en publish, produce los artefactos del build. Otro recurso debe encargarse de servir esos archivos al browser en producción.
Modelo 1: El backend sirve el frontend
El patrón más simple. El backend (Node, ASP.NET Core, etc.) toma los archivos del build de Vite y los sirve desde su propio contenedor.
var frontend = builder.AddViteApp("frontend", "./frontend").WithReference(app).WaitFor(app);
app.PublishWithContainerFiles(frontend, "./static");
En publish, Aspire copia los archivos del build del frontend dentro del contenedor del backend. Un único servicio desplegado atiende tanto la API como el frontend.
¿Cuándo usarlo?
- Tu backend ya sabe servir archivos estáticos (wwwroot, static, etc.)
- Querés una topología simple: un solo servicio, un solo endpoint público
- El frontend y el backend se despliegan y escalan juntos
Consideraciones
- El contenedor del backend crece porque incluye tanto el código del backend como los assets del frontend
- Es el modelo que Aspire favorece por defecto para frontends Vite
- Routing, auth y caché de assets se manejan en un solo lugar
Modelo 2: Un reverse proxy sirve el frontend
Un gateway o BFF (Backend For Frontend) actúa como punto de entrada público. Ruteaa las requests de API hacia el backend y sirve el build del frontend directamente.
builder
.AddYarp("app")
.WithConfiguration(c => {
c.AddRoute("/api/{**catch-all}", api)
.WithTransformPathRemovePrefix("/api");
})
.WithExternalHttpEndpoints()
.PublishWithStaticFiles(frontend);
En producción, YARP (o cualquier otro reverse proxy como Nginx o Caddy) sirve el frontend directamente mientras sigue proxeando las llamadas a la API.
¿Cuándo usarlo?
- Querés un gateway o BFF delante de toda la aplicación
- Ya usás un reverse proxy para routing, transforms o lógica BFF
- Necesitás un endpoint público único tanto en desarrollo como en producción
Consideraciones
- El hosting del frontend queda desacoplado de cualquier servicio de backend individual
- Las reglas de routing importan: el proxy decide qué va a la API y qué al frontend
- Suma un componente más al deployment, pero da mayor control sobre el ingress
Modelo 3: SPA standalone + API separada
El frontend se despliega en un static file host independiente y el browser llama directamente a la API en otro host.
Este modelo puede parecer el más familiar si venís de deployar «una app React a Netlify + una API a Heroku», pero Aspire no lo favorece y tiene varias trampas:
- Pit 1 – Variables de entorno en runtime: Vite consume las variables
VITE_*en build time. Si intentás pasar la URL de la API como variable de entorno del recurso Vite, el SPA ya deployado no la va a leer en runtime. - Pit 2 – Build args para la URL del backend: La URL del backend generalmente no se conoce cuando se buildea la imagen del frontend.
- Pit 3 – Dependencia circular: Si el frontend necesita la URL del backend y el backend necesita el origen del frontend para CORS, hay un ciclo en deploy-time que Aspire no resuelve automáticamente.
Si aún así elegís este modelo, planificá configuración de runtime explícita y gestión de CORS manual.
AddJavaScriptApp vs AddViteApp
La misma lógica aplica a AddJavaScriptApp. La diferencia es que AddViteApp conoce las convenciones de desarrollo de Vite (HMR, puerto por defecto, etc.), mientras que AddJavaScriptApp hace menos suposiciones y te deja controlar los scripts de run y build. En ambos casos, en producción necesitás decidir qué recurso sirve los assets.
Resumen: ¿Cuál elegir?
| Modelo | Entrypoint en producción | API Aspire | Ideal para |
|---|---|---|---|
| Backend sirve frontend | API / web server | PublishWithContainerFiles | Apps donde un servicio maneja API y frontend |
| Reverse proxy sirve frontend | Reverse proxy | PublishWithStaticFiles | Apps con gateway o BFF delante de la API |
| SPA standalone | Static site + API separada | Custom | Casos donde se acepta gestionar CORS y config manual |
La decisión se reduce a una pregunta: ¿quién debe ser el endpoint HTTP público en producción? Si es tu backend, usá PublishWithContainerFiles. Si es tu gateway o BFF, usá PublishWithStaticFiles.
Fuente original: Deploy JavaScript apps | Aspire Docs