← maurobernal.com.ar

Deploy de apps JavaScript con .NET Aspire: los 3 modelos que necesitás conocer

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?

ModeloEntrypoint en producciónAPI AspireIdeal para
Backend sirve frontendAPI / web serverPublishWithContainerFilesApps donde un servicio maneja API y frontend
Reverse proxy sirve frontendReverse proxyPublishWithStaticFilesApps con gateway o BFF delante de la API
SPA standaloneStatic site + API separadaCustomCasos 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