Publicando seu app de agente
Ao final do curso você tem um app de agente funcionando na sua máquina: OpenSwap depois das lições 2.1 a 2.6, ou Charta, o projeto solo. Esta página dá o próximo passo e coloca tudo online. Publicar significa transformar seu servidor Express e seu frontend Vite em um único serviço rodando em um host, em um endereço que qualquer pessoa pode abrir.
Os dois projetos têm a mesma estrutura, então ambos são publicados do mesmo jeito: um servidor Express que guarda sua chave e faz as requisições ao modelo, e um frontend que fala com ele através de caminhos como /api/agent ou /api/swaps sem hostname. Esse design, que o curso usou para manter sua chave fora do navegador, é também o que torna a publicação simples. O host define suas três variáveis, seu código compila o frontend em arquivos estáticos, e um servidor Express serve as duas partes.
Charta publica exatamente como OpenSwap. As únicas diferenças são o nome da rota e o armazenamento de conversas, e ambos estão chamados abaixo. Se você reconstruiu Charta com um framework de sua escolha, as mesmas ideias ainda valem: produza uma compilação estática do seu frontend e sirva-o do mesmo servidor que responde as rotas de API.
Uma URL pública gasta crédito de verdade
Uma vez que seu app está online, qualquer pessoa com o endereço pode fazer ele enviar requisições ao modelo, e cada requisição é cobrada na sua chave. A rota não tem login e não tem limite de gastos. Compartilhe a URL com pessoas que você quer que usem, fique de olho na página de uso do seu provedor, e leia sua chave, seu crédito abaixo antes de compartilhá-la amplamente.
O que você precisa primeiro
Tudo nesta página assume que o projeto já roda localmente. Se você ainda não o tem rodando, rodando o código localmente cobre o download, o arquivo .env, e as colisões de porta que pegam as pessoas.
Verifique as três ferramentas que a publicação precisa:
$ node --version
v24.18.0
$ npm --version
11.18.0
$ git --version
git version 2.50.1Se git diz "command not found", instale de git-scm.com. Uma conta GitHub também é necessária, porque a publicação começa de um repositório. Uma gratuita em github.com é o bastante.
npm start, você está pronto. Uma conta GitHub é a única coisa nova para configurar. npm start na sua máquina antes de fazer push, porque o host roda o mesmo código com menos lugares para esconder erros. environment.js gerado precisa de Node 20.12 ou mais novo para seu import loadEnvFile, mas isso é um piso, não uma meta. Esta página fixa o host em Node 24, o mesmo lançamento LTS recomendado para sua máquina, para que o app publicado rode no runtime que você testou. Prepare o projeto
Duas coisas precisam ser feitas antes que o código possa ir para qualquer lugar: o frontend precisa de uma compilação de produção, e o projeto precisa se tornar um repositório Git que não inclua sua chave.
Primeiro, confirme que o app ainda funciona e pare com Ctrl+C. Depois crie a compilação:
$ 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 177msA pasta dist/ é todo seu frontend, compilado em arquivos simples que um servidor pode distribuir sem Vite. Depois, verifique o .gitignore na raiz do projeto. Downloads de Scrimba podem não incluir um, então certifique-se de que essas três linhas estão em um arquivo nomeado exatamente .gitignore ao lado de package.json. As duas primeiras são as que rodando o código localmente já pediu; dist é novo, porque o host compila seu próprio:
.env
node_modules
distSe você está publicando OpenSwap da lição 2.6C, olhe também para store/conversations.json. Ele guarda cada conversa salva até agora, incluindo os exemplos com os quais veio, e tudo que está nele quando você faz commit é o que cada visitante vê na barra lateral. Para começar a cópia pública vazia, substitua o conteúdo do arquivo por uma lista vazia em vez de deletá-lo, porque o servidor o lê a cada requisição:
{ "conversations": [] }Agora os passos do Git:
$ git init
Initialized empty Git repository in path-to-your-project/.git/
$ git add .
$ git commit -m "OpenSwap, ready to deploy"
[main (root-commit) 3f8a1c2] OpenSwap, ready to deploy
19 files changed, 17382 insertions(+)Git nomeia seu primeiro branch master em algumas instalações. Se isso acontecer, renomeie-o com git branch -M main para que o resto da página corresponda.
Nunca faça commit de seu arquivo .env
Este é o mesmo aviso da página de configuração local, e importa mais agora, porque o ponto inteiro desses passos é colocar seu código no servidor de outra pessoa. Se .env é feito commit, sua chave é exposta para todos que conseguem ver o repositório. Revogue a chave e gere uma nova se isso acontecer. No host você vai definir os valores como variáveis em vez disso, e .env fica um arquivo local.
npm run build você deve ter uma pasta dist. Antes de fazer commit, execute git status: deve listar seus arquivos de projeto mas nunca .env. Se .env aparecer, corrija o .gitignore primeiro. dist é saída gerada que o host reconstrói, e node_modules é reconstruído de package.json, então ambos pertencem a .gitignore. .env é excluído porque o host nunca recebe seu arquivo de chave; seus valores se movem para as configurações de variáveis do host em vez disso. NODE_ENV=production para o passo de instalação ou limpe pacotes dev antes de compilar. O passo de empacotamento de Scrimba removeu dotenv, e environment.js só lê .env quando uma variável obrigatória está faltando do processo, então um host que fornece as três nunca precisa do arquivo. Sirva o frontend compilado do Express
Na sua máquina, npm start roda dois servidores: Express na porta 3001, e Vite na porta 5173, que encaminha chamadas /api para Express. Um host não precisa desse arranjo, e é mais simples abandoná-lo. Você muda uma linha de código e abre uma porta diferente.
Abra server.js e adicione uma linha depois de todas as rotas, logo acima de app.listen:
// Serve the built frontend from the same server
app.use(express.static("dist"));Depois tente a forma de produção localmente:
$ 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:3001Desta vez você abre a URL do Express em si, http://localhost:3001, não a do Vite. O app deve parecer e se comportar exatamente como fez sob npm start, porque o navegador agora está carregando a mesma UI de dist/ e chamando a API no mesmo endereço. Quando isso funciona, publicação é apenas "rodar esses mesmos comandos em um lugar sempre ligado". Pare com Ctrl+C.
Por que o endereço mudou
Em desenvolvimento, o proxy do Vite é o que conectava o navegador ao Express, por isso você abriu a porta 5173. Uma vez que Express serve os arquivos compilados em si, frontend e API compartilham um endereço, e o navegador chama /api na mesma origem sem proxy envolvido. A mesma origem também significa sem configuração de origem cruzada, por isso esta página nunca toca CORS.
http://localhost:3001 deve mostrar o mesmo app que você usou antes. Se faz, o projeto está pronto para publicar. express.static("dist") faz Express distribuir o frontend compilado, para que o navegador carregue o app e chame /api em uma origem, e o proxy somente dev em vite.config.js não tem nada mais para fazer. Suas rotas respondem /api, e o middleware estático responde apenas para arquivos que existem em dist, então os dois nunca competem. dist cai através do próprio 404 do Express. Se uma requisição para / responde Cannot GET /, a saída de compilação estava ausente quando o servidor iniciou, que é um problema de ordenação de compilação, não de roteamento. Publique em Render
Render é explicado aqui porque se conecta diretamente a um repositório GitHub e pede exatamente os dois comandos que você acaba de verificar. Railway e Fly.io funcionam nas mesmas entradas através de seus próprios dashboards e CLIs, e qualquer host que roda um servidor Node se comporta do mesmo jeito. Tudo que o app realmente precisa de um host é modesto: rodar node server.js, dizer qual porta ouvir, e fornecer as variáveis de ambiente.
Faça push do projeto para GitHub primeiro:
$ git remote add origin https://github.com/seu-nome/openswap.git
$ git push -u origin mainDepois, em render.com, entre, escolha New > Web Service, e conecte o repositório que você acabou de fazer push. No formulário que segue:
- Dê um nome ao serviço. O nome se torna parte do endereço público.
- Verifique que o Language é Node.
- Defina o Build Command para
npm install && npm run build. - Defina o Start Command para
npm run server. - Escolha o tipo de instância Free se você não quer pagar por hospedagem.
- Na seção de variáveis de ambiente, adicione
AI_URL,AI_KEYeAI_MODELcom os mesmos valores que você usa localmente. Se seu app tem a ferramenta de verificação de saúde do GitHub, adicioneGITHUB_TOKENtambém. - Adicione uma variável a mais,
NODE_VERSION, com o valor24, para que o host rode a mesma versão de Node que você usa localmente.
Escolha Create Web Service e o dashboard transmite os logs. Uma primeira publicação saudável parece seu teste local, com uma porta que o host escolheu:
==> 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 liveO localhost nessa linha é o app se descrevendo de dentro do host. O endereço para compartilhar é mostrado no dashboard, algo como https://seu-app.onrender.com. Abra e use o app como um visitante faria.
O comando de início é npm run server, não npm start. Na sua máquina npm start é conveniente porque inicia Vite junto com Express, mas em um host o frontend compilado já está sendo servido por Express, e um segundo processo Vite apenas giraria sem usar. Charta é idêntico, exceto que sua rota é /api/agent, não tem uma linha GITHUB_TOKEN, e sua linha de log lê Charta server running at....
Planejando dividir o frontend e a API em vez disso?
Hosts estáticos como GitHub Pages ou um site Netlify podem publicar um frontend compilado, mas não conseguem rodar server.js, então a API precisa de seu próprio serviço. No momento em que as duas partes vivem em origens diferentes, o navegador bloqueia chamadas /api e você está em território CORS. Para este curso, um serviço como descrito acima evita tudo isso.
onrender.com. Se as marcações estão faltando, as variáveis são a coisa para corrigir, não seu código. PORT é deixado não definido: server.js já lê process.env.PORT, então a escolha de porta do host flui direto para app.listen. NODE_VERSION supera .node-version, .nvmrc e engines em package.json, então fixa o runtime mesmo quando o padrão de Render para novos serviços se move. Render também define NODE_ENV=production apenas em tempo de execução, por isso o passo de compilação ainda instala Vite. Sua chave, seu crédito
As três variáveis fazem o mesmo trabalho que faziam em Scrimba e em seu .env local, e ainda falham como um conjunto em vez de individualmente. Uma chave válida de OpenAI contra a URL de OpenRouter falha tão alto em produção quanto falhou no navegador. Se você trocar de provedor, mude AI_URL, AI_KEY e AI_MODEL juntos nas configurações do host, exatamente como configuração de provedor descreve.
Duas diferenças da configuração local merecem atenção.
Primeiro, se você publicou uma lição OpenSwap mais recente, defina GITHUB_TOKEN no host também. Na sua máquina, o limite de taxa anônimo do GitHub está ligado ao seu IP. Em um host, seu app compartilha intervalos IP de saída com todo outro inquilino dessa plataforma, então requisições anônimas do GitHub terminam muito mais rápido e a ferramenta de verificação de saúde pode começar a retornar 403s mesmo quando funciona localmente. Adicionar o token é a diferença.
Segundo, a URL é pública e a chave é sua. Um app publicado é um bom lugar para aplicar o próprio conselho do curso sobre chaves: dê à publicação sua própria chave para que possa ser revogada separadamente, verifique a página de uso do provedor depois de compartilhar o link, e escolha o modelo com a lista recomendada em mente, porque um modelo mais pesado em uma URL pública se acumula rápido. Se uma chave vazar, revogue-a primeiro e limpe depois.
PORT não definido, porque o host o escolhe. Antes de compartilhar o endereço, abra a página de uso do seu provedor uma vez para que você saiba onde procurar quando o link começar a receber tráfego. process.env que o local; apenas a fonte dos valores mudou. Quando o host fornece as três, environment.js pula .env inteiramente. Quando uma está faltando, ele para o servidor com a mesma mensagem que você viu localmente, e seu conselho para completar .env significa, em um host, complete as variáveis no dashboard. AI_URL, AI_KEY e AI_MODEL consistentes como um conjunto, e note que rotas grátis do OpenRouter em produção carregam compromissos de disponibilidade e política de dados que importam mais sob uma URL pública do que em um scrim. O que sobrevive a um reinício e o que não
O curso é honesto sobre seus armazenamentos de conversa, e hospedagem não muda isso, apenas adiciona mais maneiras para um servidor reiniciar.
Em Charta, conversas vivem em um Map na memória do servidor, e um reinício as limpa. Na forma final do OpenSwap, conversas são salvas em store/conversations.json, que sobrevive um reinício na sua máquina. Em um host geralmente não. Os serviços web livres do Render têm um sistema de arquivos efêmero: qualquer coisa que o app escreve é perdida toda vez que o serviço reimplanta, reinicia ou é desligado, e serviços livres são desligados após 15 minutos sem tráfego. O arquivo volta à cópia em seu repositório, então conversas salvas vêm e vão. Esse é o limite que o curso já nomeia: o histórico de conversa é uma conveniência de demonstração, e um produto real coloca sessões e um banco de dados atrás disso. Publicar não transforma nenhum armazenamento em um banco de dados, e não é feito para isso.
Uma consequência mais de um armazenamento compartilhado único: OpenSwap não tem contas, então toda pessoa que abre seu endereço vê a mesma lista de conversa e pode reabrir qualquer uma. Mantenha isso em mente antes de compartilhar o link.
Tudo mais se comporta como você esperaria. O contexto do agente é carregado na requisição do mesmo jeito que era localmente, o modelo vê as mesmas ferramentas, e o objetivo de extensão de busca web do Charta, que só existe na OpenAI API, funciona no host exatamente como funciona em casa porque OpenAI roda essa busca para você.
Atualizar o app é um comando:
$ git pushRender vê o novo commit no branch conectado, roda o comando de compilação, e muda o tráfego para a nova versão uma vez que ela inicia. Se a compilação falhar, a versão anterior fica ativa, e o log de publicação mostra por quê.
conversations.json comprometido é o estado que todo reinício volta a. contextHistory só precisa sobreviver entre mensagens, que ambos os armazenamentos gerenciam dentro de uma vida útil de processo. Se algo não funcionar
Os logs dizem Missing environment variables: AI_KEY. O host não tem essa variável ainda, então a verificação de ambiente parou o servidor propositalmente. A próxima linha diz a você para completar .env, que é a correção local; no host, abra a página Environment do serviço, adicione os nomes faltantes, e escolha Save and deploy. Seu .env local nunca está em Git, então o host tem que ser informado cada valor diretamente.
Cannot GET /. Express começou sem encontrar nada em dist. Ou o comando de compilação não rodou npm run build, ou app.use(express.static("dist")) está faltando de server.js. Verifique o comando de compilação, depois o log do passo de compilação para um erro de Vite.
Seu serviço nunca fica ativo. Algo parou o servidor antes dele ouvir em uma porta. Role para cima no log de publicação até o primeiro erro: é geralmente a verificação de ambiente acima, uma compilação falhada, ou um comando de início diferente de npm run server.
Um erro de modelo não encontrado ou autenticação. As três variáveis discordam, por exemplo uma ID de modelo de OpenAI contra a URL de OpenRouter. Mude as três juntas nas configurações do host, como configuração de provedor explica.
"Local environment loading requires Node.js 20.12 or newer." O Node do host é mais antigo que o piso do projeto. Defina NODE_VERSION para 24 e reimplante.
A ferramenta de verificação de saúde retorna 403 ou 429. Requisições anônimas do GitHub compartilham limites de taxa com outros inquilinos do host. Defina GITHUB_TOKEN nas configurações do host e reimplante.
A primeira requisição leva um minuto. Serviços livres de Render são desligados após 15 minutos sem tráfego, e ligar novamente leva cerca de um minuto. Espere a página carregar; as requisições depois são rápidas. Um desligamento também limpa conversas salvas, como a seção anterior descreve.
Um erro CORS no console do navegador. O frontend e a API estão em origens diferentes, o que só acontece se você os divide entre dois serviços. Mantenha-os como um serviço como esta página faz, ou adicione tratamento CORS ao serviço de API se uma divisão é o que você quer.
Tudo parece certo e ainda falha. Traga para o Discord do curso com os logs de publicação do host, não apenas o erro do navegador. Problemas de publicação quase sempre são visíveis nos logs do serviço.
dist ou não, o comando de início ou chegou à linha de listen ou não, e um serviço ativo ou tem as variáveis ou não. A verificação de ambiente na inicialização nomeia cada variável que validou. 
