Skip to content
This page has been auto-translated and may contain errors.View in English

Poniendo tu app de agente en línea ​

Al final del curso tienes una app de agente funcional en tu máquina: OpenSwap después de las lecciones 2.1 a 2.6, o Charta, tu proyecto final. Esta página da el siguiente paso y la pone en línea. Desplegar significa convertir tu servidor Express y tu frontend de Vite en un único servicio ejecutándose en un host, en una dirección que cualquiera puede abrir.

Ambos proyectos tienen la misma estructura, así que ambos se despliegan igual: un servidor Express que sostiene tu clave y hace las solicitudes al modelo, y un frontend que se comunica con él a través de rutas como /api/agent o /api/swaps sin hostname adjunto. Este diseño, que el curso usó para mantener tu clave fuera del navegador, es también lo que hace que el despliegue sea simple. El host configura tus tres variables, tu código compila el frontend en archivos estáticos, y un único servidor Express sirve ambas mitades.

Charta se despliega exactamente como OpenSwap. Sus únicas diferencias son el nombre de la ruta y el almacén de conversaciones, y ambas se señalan abajo. Si reconstruiste Charta con un framework de tu propia elección, las mismas ideas se aplican: produce una compilación estática de tu frontend y sírvela desde el mismo servidor que responde las rutas de la API.

Una URL pública gasta crédito real

Una vez que tu app esté en línea, cualquiera con la dirección puede hacer que envíe solicitudes al modelo, y cada solicitud se factura a tu clave. La ruta no tiene login ni límite de gasto. Comparte la URL con las personas que quieres que la usen, mantén un ojo en la página de uso de tu proveedor, y lee tu clave, tu crédito abajo antes de compartirla ampliamente.

Lo que necesitas primero ​

Todo en esta página asume que el proyecto ya se ejecuta localmente. Si aún no lo tienes funcionando, Ejecutar el código localmente cubre la descarga, el archivo .env, y las colisiones de puertos que atrapan a la gente.

Verifica las tres herramientas que necesita el despliegue:

bash
$ node --version
v24.18.0
$ npm --version
11.18.0
$ git --version
git version 2.50.1

Si git dice "command not found", instálalo desde git-scm.com. También necesitas una cuenta de GitHub, porque el despliegue comienza desde un repositorio. Una gratuita en github.com es suficiente.

JunoLo que necesitas primero Si los tres comandos imprimen un número de versión y la app funciona bajo npm start, estás listo. Una cuenta de GitHub es lo único nuevo que debes configurar.
JunoLo que necesitas primero Desplegar es un paso de copia, así que hereda cada problema local. Arregla cualquier cosa que falle con npm start en tu máquina antes de enviar, porque el host ejecuta el mismo código con menos lugares donde esconder errores.
JunoLo que necesitas primero El archivo environment.js generado necesita Node 20.12 o más reciente para su importación de loadEnvFile, pero eso es un mínimo, no un objetivo. Esta página fija el host a Node 24, la misma versión LTS recomendada para tu máquina, así que la app desplegada se ejecuta en el runtime que probaste.

Preparar el proyecto ​

Dos cosas necesitan hacerse antes de que el código pueda ir a cualquier lado: el frontend necesita una compilación de producción, y el proyecto necesita convertirse en un repositorio de Git que no incluya tu clave.

Primero, confirma que la app aún funciona y deténla con Ctrl+C. Luego crea la compilación:

bash
$ npm run build
vite v6.4.3 building for production...
✓ 8 modules transformed.
dist/index.html                   4.08 kB │ gzip:  1.35 kB
dist/assets/index-C6csgGji.css    8.01 kB │ gzip:  2.41 kB
dist/assets/index-Ck6PtvSY.js   105.89 kB │ gzip: 38.41 kB
✓ built in 177ms

La carpeta dist/ es todo tu frontend, compilado en archivos planos que un servidor puede distribuir sin Vite. Luego, verifica el .gitignore en la raíz del proyecto. Las descargas de Scrimba pueden no incluir uno, así que asegúrate de que estas tres líneas estén en un archivo llamado exactamente .gitignore junto a package.json. Las dos primeras son las que Ejecutar el código localmente ya pidió; dist es nueva, porque el host construye la suya:

txt
.env
node_modules
dist

Si estás desplegando OpenSwap de la lección 2.6C, mira también store/conversations.json. Contiene cada conversación guardada hasta ahora, incluyendo los ejemplos con los que vino, y lo que esté en él cuando hagas commit es lo que cada visitante ve en la barra lateral. Para iniciar la copia pública vacía, reemplaza el contenido del archivo con una lista vacía en lugar de eliminarlo, porque el servidor lo lee en cada solicitud:

json
{ "conversations": [] }

Ahora los pasos de Git:

bash
$ git init
Initialized empty Git repository in path-to-your-project/.git/
$ git add .
$ git commit -m "OpenSwap, listo para desplegar"
[main (root-commit) 3f8a1c2] OpenSwap, listo para desplegar
 19 files changed, 17382 insertions(+)

Git nombra su primera rama master en algunas instalaciones. Si eso sucede, renómbrala con git branch -M main para que el resto de la página coincida.

Nunca hagas commit de tu archivo .env

Esta es la misma advertencia que la página de configuración local, y importa más ahora, porque el todo el punto de estos pasos es poner tu código en el servidor de alguien más. Si .env está confirmado, tu clave está expuesta a todos quienes pueden ver el repositorio. Revoca la clave y genera una nueva si eso sucede. En el host configurarás los valores como variables en su lugar, y .env sigue siendo un archivo local.

JunoPreparar el proyecto Después de npm run build deberías tener una carpeta dist. Antes de hacer commit, ejecuta git status: debería listar los archivos de tu proyecto pero nunca .env. Si .env aparece, primero arregla el .gitignore.
JunoPreparar el proyectodist es salida generada que el host reconstruye, y node_modules se reconstruye desde package.json, así que ambas pertenecen a .gitignore. .env está excluida porque el host nunca recibe tu archivo de clave; tus valores se mueven a la configuración de variables del host en su lugar.
JunoPreparar el proyecto La compilación debe ejecutarse con devDependencies instaladas, porque Vite vive allí, así que no establezcas NODE_ENV=production para el paso de instalación ni podes paquetes de desarrollo antes de compilar. El paso de empaquetamiento de Scrimba eliminó dotenv, y environment.js solo lee .env cuando una variable requerida falta del proceso, así que un host que suministra las tres nunca necesita el archivo.

Servir el frontend compilado desde Express ​

En tu máquina, npm start ejecuta dos servidores: Express en el puerto 3001, y Vite en el puerto 5173, que reenvía las llamadas /api a Express. Un host no necesita esa disposición, y es más simple abandonarla. Cambias una línea de código y abres un puerto diferente.

Abre server.js y agrega una línea después de todas las rutas, justo arriba de app.listen:

js
// Servir el frontend compilado desde el mismo servidor
app.use(express.static("dist"));

Luego prueba la forma de producción localmente:

bash
$ npm run server
Environment check:
✓ AI_KEY: configured
✓ AI_MODEL: gpt-5.4-nano
✓ AI_URL: https://api.openai.com/v1
○ Optional environment not configured: GITHUB_TOKEN
OpenSwap server running at http://localhost:3001

Esta vez abres la URL de Express directamente, http://localhost:3001, no la de Vite. La app debe verse y comportarse exactamente como lo hizo bajo npm start, porque el navegador ahora carga la misma UI desde dist/ y llama a la API en la misma dirección. Cuando eso funcione, el despliegue es solo "ejecutar estos mismos comandos en algún lugar que esté siempre activo". Detén con Ctrl+C.

Por qué cambió la dirección

En desarrollo, el proxy de Vite es lo que conectaba el navegador a Express, por eso abrías el puerto 5173. Una vez que Express sirve los archivos compilados, el frontend y la API comparten una dirección, y el navegador llama /api en el mismo origen sin proxy involucrado. El mismo origen también significa sin configuración de origen cruzado, por eso esta página nunca toca CORS.

JunoServir el frontend compilado desde Express Solo una línea de terminal anuncia un servidor esta vez, la de Express, y http://localhost:3001 debería mostrar la misma app que usaste antes. Si lo hace, el proyecto está listo para desplegar.
JunoServir el frontend compilado desde Expressexpress.static("dist") hace que Express distribuya el frontend compilado, así que el navegador carga la app y llama /api en un origen, y el proxy solo para desarrollo en vite.config.js no tiene nada más que hacer. Tus rutas responden /api, y el middleware estático responde solo para archivos que existen en dist, así que los dos nunca compiten.
JunoServir el frontend compilado desde Express No hay enrutamiento del lado del cliente en ninguna app, así que no se necesita una ruta de respaldo de SPA: un fallo en dist cae a través del propio 404 de Express. Si una solicitud para / responde Cannot GET /, la salida de compilación faltaba cuando el servidor comenzó, que es un problema de ordenamiento de compilación, no de enrutamiento.

Desplegar en Render ​

Render se explica aquí porque se conecta directamente a un repositorio de GitHub y pide exactamente los dos comandos que acabas de verificar. Railway y Fly.io funcionan con las mismas entradas a través de sus propios paneles y CLIs, y cualquier host que ejecute un servidor Node se comporta de la misma manera. Lo que la app realmente necesita de un host es modesto: ejecutar node server.js, decirle en qué puerto escuchar, y proporcionar las variables de entorno.

Primero, envía el proyecto a GitHub:

bash
$ git remote add origin https://github.com/tu-nombre/openswap.git
$ git push -u origin main

Luego, en render.com, inicia sesión, elige New > Web Service, y conecta el repositorio que acabas de enviar. En el formulario que sigue:

  1. Dale al servicio un nombre. El nombre se convierte en parte de la dirección pública.
  2. Verifica que el Language sea Node.
  3. Establece el Build Command a npm install && npm run build.
  4. Establece el Start Command a npm run server.
  5. Elige el tipo de instancia Free si no quieres pagar por alojamiento.
  6. En la sección de variables de entorno, agrega AI_URL, AI_KEY y AI_MODEL con los mismos valores que usas localmente. Si tu app tiene la herramienta de verificación de salud de GitHub, agrega también GITHUB_TOKEN.
  7. Agrega una variable más, NODE_VERSION, con el valor 24, para que el host ejecute la misma versión de Node que usas localmente.

Elige Create Web Service y el panel muestra los registros. Un primer despliegue saludable se ve como tu prueba local, con un puerto que el host eligió:

text
==> Running 'npm run server'
Environment check:
✓ AI_KEY: configured
✓ AI_MODEL: gpt-5.4-nano
✓ AI_URL: https://api.openai.com/v1
✓ Optional environment: GITHUB_TOKEN
OpenSwap server running at http://localhost:10000
==> Your service is live

El localhost en esa línea es la app describiéndose desde dentro del host. La dirección para compartir se muestra en el panel, algo como https://tu-app.onrender.com. Abrela y usa la app como lo haría un visitante.

El comando de inicio es npm run server, no npm start. En tu máquina npm start es conveniente porque inicia Vite junto a Express, pero en un host el frontend compilado ya está siendo servido por Express, y un segundo proceso de Vite solo giraría sin uso. Charta es idéntica, excepto que su ruta es /api/agent, no tiene línea GITHUB_TOKEN, y su línea de registro dice Charta server running at....

¿Planeas dividir el frontend y la API?

Los hosts estáticos como GitHub Pages o un sitio de Netlify pueden publicar un frontend compilado, pero no pueden ejecutar server.js, así que la API necesita su propio servicio. En el momento en que las dos mitades viven en orígenes diferentes, el navegador bloquea las llamadas /api y estás en territorio CORS. Para este curso, un servicio como se describe arriba evita todo.

JunoDesplegar en Render Los registros deben mostrar la misma verificación de entorno que viste localmente, todos los ticks, y luego la línea del servidor. Solo entonces abre la dirección onrender.com. Si faltan los ticks, las variables son lo que debes arreglar, no tu código.
JunoDesplegar en Render El host clona el repositorio, ejecuta el comando de compilación, luego ejecuta el comando de inicio y enruta el tráfico público al puerto en el que la app escucha. Por eso PORT se deja sin establecer: server.js ya lee process.env.PORT, así que la elección de puerto del host fluye directamente a app.listen.
JunoDesplegar en Render Los dos comandos son el contrato completo, así que cualquier host de Node que acepte un comando de compilación e inicio funciona sin cambios. En Render, NODE_VERSION supera a .node-version, .nvmrc y engines en package.json, así que fija el runtime incluso cuando el defecto de Render para servicios nuevos avanza. Render también establece NODE_ENV=production solo en tiempo de ejecución, por eso el paso de compilación aún instala Vite.

Tu clave, tu crédito ​

Las tres variables hacen el mismo trabajo que hacían en Scrimba y en tu .env local, y siguen fallando como un conjunto en lugar de individualmente. Una clave OpenAI válida contra la URL de OpenRouter falla tan ruidosamente en producción como lo hizo en el navegador. Si cambias de proveedor, cambia AI_URL, AI_KEY y AI_MODEL juntos en la configuración del host, exactamente como configuración del proveedor describe.

Dos diferencias de la configuración local merecen atención.

Primero, si desplegaste una lección posterior de OpenSwap, establece también GITHUB_TOKEN en el host. En tu máquina, el límite de tasa anónimo de GitHub está adjunto a tu IP. En un host, tu app comparte rangos de IP salientes con cada otro inquilino de esa plataforma, así que las solicitudes anónimas de GitHub se agotan mucho más rápido y la herramienta de verificación de salud puede empezar a devolver 403s incluso cuando funciona localmente. Agregar el token es la diferencia.

Segundo, la URL es pública y la clave es tuya. Una app desplegada es un buen lugar para aplicar el propio consejo del curso sobre claves: dale al despliegue su propia clave para que pueda ser revocada por separado, verifica la página de uso de tu proveedor después de compartir el enlace, y elige el modelo con la lista recomendada en mente, porque un modelo más pesado en una URL pública se acumula rápidamente. Si una clave se filtra, revócala primero y limpia después.

JunoTu clave, tu crédito Usa los mismos tres valores que funcionan en Scrimba y en tu máquina, y deja PORT sin establecer, porque el host lo elige. Antes de compartir la dirección, abre la página de uso de tu proveedor una vez para que sepas dónde mirar cuando el enlace comienza a obtener tráfico.
JunoTu clave, tu crédito La app desplegada lee los mismos nombres de process.env que la local; solo cambió la fuente de los valores. Cuando el host suministra los tres, environment.js salta .env completamente. Cuando falta uno, detiene el servidor con el mismo mensaje que viste localmente, y su consejo de completar .env significa, en un host, completar las variables en el panel.
JunoTu clave, tu crédito Trata el despliegue como un punto final sin autenticación que gasta crédito. Usa una clave dedicada con su propio presupuesto, mantén AI_URL, AI_KEY y AI_MODEL consistentes como un conjunto, y nota que las rutas de OpenRouter gratuitas en producción conllevan compromisos de disponibilidad y política de datos que importan más bajo una URL pública que en una scrim.

Qué sobrevive a un reinicio y qué no ​

El curso es honesto sobre sus almacenes de conversaciones, y el alojamiento no cambia eso, solo agrega más formas en que un servidor puede reiniciarse.

En Charta, las conversaciones viven en un Map en la memoria del servidor, y un reinicio las limpia. En la forma final de OpenSwap, las conversaciones se guardan en store/conversations.json, que sobrevive a un reinicio en tu máquina. En un host generalmente no. Los servicios web gratuitos de Render tienen un sistema de archivos efímero: cualquier cosa que la app escriba se pierde cada vez que el servicio se redespliega, se reinicia, o se detiene, y los servicios gratuitos se detienen después de 15 minutos sin tráfico. El archivo regresa a la copia en tu repositorio, así que las conversaciones guardadas van y vienen. Ese es el límite que el curso ya nombra: el historial de conversaciones es una conveniencia de demostración, y un producto real pone sesiones y una base de datos detrás. Desplegar no convierte ninguno de los dos almacenes en una base de datos, y no se pretende que lo haga.

Una consecuencia más de un almacén compartido único: OpenSwap no tiene cuentas, así que todos quienes abren tu dirección ven la misma lista de conversaciones y pueden reabrir cualquiera de ellas. Ten eso en mente antes de compartir el enlace.

Todo lo demás se comporta como esperarías. El contexto del agente se lleva en la solicitud del mismo modo que lo fue localmente, el modelo ve las mismas herramientas, y el objetivo de estiramiento de búsqueda web de Charta, que solo existe en la API de OpenAI, funciona en el host exactamente como lo hace en casa porque OpenAI ejecuta esa búsqueda para ti.

Actualizar la app es un comando:

bash
$ git push

Render ve el nuevo commit en la rama conectada, ejecuta el comando de compilación, y cambia el tráfico a la nueva versión una vez que comienza. Si la compilación falla, la versión anterior sigue en vivo, y el registro de despliegue muestra por qué.

JunoQué sobrevive a un reinicio y qué no Espera que las conversaciones guardadas desaparezcan después de un reinicio, un redespliegue, o un período tranquilo. Ese es el almacén de demostración del curso encontrándose con un disco de host, no un error en tu despliegue.
JunoQué sobrevive a un reinicio y qué no El Map de Charta vive en la memoria del proceso, así que termina con el proceso. El archivo de OpenSwap vive en el disco, pero en un host gratuito el disco se reemplaza desde tu repositorio cada vez que lo es la instancia, así que el conversations.json confirmado es el estado con el que todos los reinicios regresan.
JunoQué sobrevive a un reinicio y qué no Las conversaciones duraderas necesitan almacenamiento que sobreviva a la instancia: un disco persistente, que los servicios web gratuitos de Render no pueden usar, o una base de datos externa, con sesiones en la parte superior si los visitantes solo deben ver su propio historial. Nada de eso es requerido por el curso. La garantía de enseñanza es más estrecha: contextHistory solo tiene que sobrevivir entre mensajes, lo que ambos almacenes manejan dentro de una vida útil de un proceso.

Si algo no funciona ​

Los registros dicen Missing environment variables: AI_KEY. El host aún no tiene esa variable, así que la verificación de entorno detuvo el servidor a propósito. La siguiente línea te dice que completes .env, que es la corrección local; en el host, abre la página Environment del servicio, agrega los nombres faltantes, y elige Save and deploy. Tu .env local nunca está en Git, así que el host debe ser informado de cada valor directamente.

Cannot GET /. Express comenzó sin encontrar nada en dist. O el comando de compilación no ejecutó npm run build, o app.use(express.static("dist")) falta de server.js. Verifica el comando de compilación, luego el registro del paso de compilación para un error de Vite.

Tu servicio nunca se pone en vivo. Algo detuvo el servidor antes de escuchar en un puerto. Desplázate hacia arriba en el registro de despliegue hasta el primer error: usualmente es la verificación de entorno arriba, una compilación fallida, o un comando de inicio que no es npm run server.

Un error de modelo no encontrado o autenticación. Las tres variables no están de acuerdo, por ejemplo una ID de modelo de OpenAI contra la URL de OpenRouter. Cambia las tres juntas en la configuración del host, como configuración del proveedor explica.

"Local environment loading requires Node.js 20.12 or newer." El Node del host es más viejo que el mínimo del proyecto. Establece NODE_VERSION a 24 y redesplieg.

La herramienta de verificación de salud devuelve 403 o 429. Las solicitudes anónimas de GitHub comparten límites de tasa con los otros inquilinos del host. Establece GITHUB_TOKEN en la configuración del host y redesplieg.

La primera solicitud tarda un minuto. Los servicios gratuitos de Render se detienen después de 15 minutos sin tráfico, y volver a encender tarda aproximadamente un minuto. Espera a que la página cargue; las solicitudes después de ella son rápidas. Una parada también limpia las conversaciones guardadas, como la sección anterior describe.

Un error CORS en la consola del navegador. El frontend y la API están en orígenes diferentes, lo que solo sucede si los divides entre dos servicios. Mantenlos como un servicio como esta página hace, o agrega manejo de CORS al servicio de API si una división es lo que quieres.

Todo se ve correcto y aún falla. Tráelo al Discord del curso con los registros de despliegue del host, no solo el error del navegador. Los problemas de despliegue casi siempre son visibles en los registros del servicio.

JunoSi algo no funciona Comienza en los registros del host, no en el navegador. Encuentra la primera línea de error, coincídela con la lista anterior, y despliega nuevamente después de cambiar una configuración para que el servicio realmente la recoja.
JunoSi algo no funciona Lee los fallos por etapa: el comando de compilación produjo dist o no, el comando de inicio llegó a la línea de escucha o no, y un servicio en vivo tiene las variables o no. La verificación de entorno al inicio nombra cada variable que validó.
JunoSi algo no funciona Distingue fallos de tiempo de despliegue de tiempo de solicitud. Los errores de compilación y inicio aparecen en el registro de despliegue, y en cualquier despliegue después del primero, Render mantiene la versión anterior sirviendo mientras lo hace, así que un envío roto puede parecer que nada sucedió. Los errores de tiempo de ejecución como límites de tasa 403 o desajustes de modelo solo aparecen cuando llega una solicitud, así que reproduce uno con un mensaje real antes de culpar al despliegue.