Você sabe construir aplicações. Mas construir é apenas metade do trabalho — a outra metade é colocar no ar para que outras pessoas possam usar. Deploy é o processo de levar o código do seu computador para um servidor acessível pela internet.
Muitos desenvolvedores excelentes na escrita de código travam quando precisam fazer deploy. Isso acontece porque deploy envolve um conjunto diferente de conceitos — servidores, variáveis de ambiente, builds, domínios, certificados SSL. Este artigo desmistifica tudo isso.
Vamos cobrir deploy do front-end React e do back-end Node.js, usando as plataformas mais usadas pelo mercado em 2025.
O que acontece em um deploy
Antes de qualquer comando, é importante entender conceitualmente o que estamos fazendo. O processo é sempre o mesmo, independente da plataforma.
Para o front-end React, o código precisa ser compilado — o Vite transforma JSX, importações e otimizações em arquivos HTML, CSS e JavaScript puros que qualquer navegador entende. Esses arquivos estáticos são então colocados em um servidor que os entrega ao navegador quando alguém acessa a URL.
Para o back-end Node.js, o processo é diferente — o código não é compilado para o usuário final, mas precisa rodar em um servidor com Node instalado, conectado ao banco de dados, com as variáveis de ambiente configuradas e disponível 24 horas por dia.
Desenvolvimento Produção
──────────────────────── ────────────────────────────────
npm run dev npm run build → arquivos estáticos
localhost:5173 CDN global (Vercel, Netlify)
Hot reload automático Arquivos servidos ao edge
node src/index.js Processo gerenciado (PM2, Railway)
localhost:3000 Servidor com IP público
.env local Variáveis de ambiente na plataforma
MongoDB local MongoDB Atlas (nuvem)
Deploy do front-end — Vercel
A Vercel é a plataforma mais popular para deploy de aplicações React e foi criada pelos autores do Next.js. O deploy é tão simples que parece mágica — você conecta o repositório do GitHub e a Vercel cuida de todo o resto.
Preparando o projeto React para produção
Antes do deploy, precisamos garantir que o projeto está configurado corretamente. O passo mais importante é usar variáveis de ambiente para a URL da API — nunca escreva URLs hardcoded no código.
# Variáveis de ambiente no Vite usam o prefixo VITE_
# Apenas variáveis com este prefixo ficam acessíveis no código do navegador
# (por segurança — não queremos expor segredos de servidor no front-end)
# .env.development — usado em npm run dev
VITE_API_URL=http://localhost:3000
# .env.production — usado em npm run build
VITE_API_URL=https://sua-api.railway.app
// src/services/api.js
// import.meta.env é a forma do Vite acessar variáveis de ambiente
// Em desenvolvimento: http://localhost:3000
// Em produção: https://sua-api.railway.app
const BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000';
export async function api(endpoint, opcoes = {}) {
// ... resto da implementação
}
// vite.config.js
// Configurações importantes para produção
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
// Divide o bundle em chunks menores para carregamento mais rápido
// Cada rota lazy() vira um arquivo separado
rollupOptions: {
output: {
// Agrupa bibliotecas grandes em um chunk separado
// Isso melhora o cache — react e react-dom raramente mudam
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
query: ['@tanstack/react-query'],
},
},
},
// Avisa quando algum chunk ultrapassar 500KB
chunkSizeWarningLimit: 500,
},
});
Para SPAs com React Router, precisamos de um arquivo especial que instrui a Vercel a sempre servir o index.html, independente da rota acessada. Sem isso, acessar diretamente /produtos/42 retornaria um 404.
// vercel.json — na raiz do projeto
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
Deploy na Vercel passo a passo
# Opção 1 — via CLI (mais rápido para testar)
npm install -g vercel
vercel login
vercel # deploy para preview
vercel --prod # deploy para produção
# Opção 2 — via GitHub (recomendado para projetos contínuos)
# 1. Suba o código para o GitHub
# 2. Acesse vercel.com e clique em "New Project"
# 3. Importe o repositório
# 4. Configure as variáveis de ambiente na interface:
# VITE_API_URL = https://sua-api.railway.app
# 5. Clique em Deploy
#
# A partir daí, cada push para main faz deploy automático
# Pull Requests ganham URLs de preview automáticas
Deploy do front-end — Netlify
A Netlify é outra excelente opção, especialmente popular para projetos open source. O processo é similar à Vercel.
# netlify.toml — na raiz do projeto
[build]
# Comando que gera os arquivos de produção
command = "npm run build"
# Pasta com os arquivos gerados pelo build
publish = "dist"
# Redirect para SPA — sem isso, rotas diretas retornam 404
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
# Via CLI
npm install -g netlify-cli
netlify login
netlify deploy --build # preview
netlify deploy --build --prod # produção
Build local — sempre teste antes do deploy
Um erro comum é fazer push e descobrir que o build de produção falha por algo que funcionava em desenvolvimento. Desenvolva o hábito de testar o build localmente antes de enviar.
# Gera os arquivos de produção na pasta dist/
npm run build
# Serve os arquivos de produção localmente
# Isso simula exatamente como vai funcionar no servidor
npm run preview
# Abra http://localhost:4173 e teste tudo:
# - navegação entre rotas
# - login e autenticação
# - chamadas à API
# - lazy loading das páginas
Deploy do back-end — Railway
O Railway é a plataforma mais simples para fazer deploy de aplicações Node.js com banco de dados. Ele detecta automaticamente que é um projeto Node e configura tudo.
Preparando a API para produção
O primeiro passo é garantir que a aplicação lê corretamente as variáveis de ambiente e que não há nada hardcoded.
// src/config/index.js
// Centraliza toda a configuração em um lugar
// Se alguma variável obrigatória estiver faltando, a aplicação
// para imediatamente com uma mensagem clara — melhor do que falhar silenciosamente
require('dotenv').config();
const config = {
porta: Number(process.env.PORT) || 3000,
ambiente: process.env.NODE_ENV || 'development',
mongoUrl: process.env.MONGODB_URL,
jwtSecret: process.env.JWT_SECRET,
jwtExpiracao: process.env.JWT_EXPIRA_EM || '7d',
// Helpers para verificar o ambiente atual
eDev: process.env.NODE_ENV === 'development',
eProd: process.env.NODE_ENV === 'production',
eTeste: process.env.NODE_ENV === 'test',
};
// Valida variáveis obrigatórias no startup
// Em produção, se MONGODB_URL não existir, é melhor parar agora
// do que falhar misteriosamente na primeira requisição ao banco
const obrigatorias = ['MONGODB_URL', 'JWT_SECRET'];
const faltando = obrigatorias.filter((v) => !process.env[v]);
if (faltando.length > 0 && config.eProd) {
console.error(
`[Config] Variáveis de ambiente obrigatórias faltando: ${faltando.join(', ')}`
);
process.exit(1);
}
module.exports = config;
// src/index.js
// Ajustes importantes para produção
const express = require('express');
const cors = require('cors');
const config = require('./config');
const { conectar } = require('./config/database');
const app = express();
// CORS configurado para aceitar apenas origens conhecidas em produção
// Em desenvolvimento, aceita qualquer origem para facilitar os testes
const origemPermitida = config.eProd
? process.env.FRONTEND_URL // Ex: https://meuapp.vercel.app
: '*';
app.use(
cors({
origin: origemPermitida,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
})
);
app.use(express.json({ limit: '10mb' }));
// Health check — endpoint simples que o Railway e outros serviços
// usam para saber se a aplicação está viva
// Se retornar 200, o serviço está ok. Se não responder, reinicia o processo.
app.get('/health', (req, res) => {
res.json({
status: 'ok',
ambiente: config.ambiente,
uptime: Math.floor(process.uptime()) + 's',
timestamp: new Date().toISOString(),
});
});
// Rotas da API
app.use('/auth', require('./routes/auth'));
app.use('/produtos', require('./routes/produtos'));
app.use('/tarefas', require('./routes/tarefas'));
// Middlewares de erro
app.use(require('./middlewares/erros').naoEncontrado);
app.use(require('./middlewares/erros').tratadorDeErros);
// Inicialização
conectar().then(() => {
app.listen(config.porta, () => {
console.log(`[Server] Rodando na porta ${config.porta} (${config.ambiente})`);
});
});
// Encerramento gracioso — finaliza requisições em andamento antes de parar
// O Railway envia SIGTERM antes de reiniciar o container
process.on('SIGTERM', () => {
console.log('[Server] SIGTERM recebido. Encerrando...');
process.exit(0);
});
module.exports = app;
// package.json — scripts importantes para produção
{
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "jest"
},
"engines": {
"node": ">=18.0.0"
}
}
O campo engines instrui o Railway (e outras plataformas) sobre qual versão do Node usar. Sempre especifique.
Deploy no Railway
# Via CLI
npm install -g @railway/cli
railway login
railway init # inicializa o projeto no Railway
railway up # faz o deploy
# Configurando variáveis de ambiente via CLI
railway variables set NODE_ENV=production
railway variables set JWT_SECRET=sua-chave-super-secreta-aqui
railway variables set MONGODB_URL=mongodb+srv://...
railway variables set FRONTEND_URL=https://seuapp.vercel.app
Também é possível configurar tudo pela interface web do Railway, que é mais visual e recomendada para iniciantes.
MongoDB Atlas — banco de dados em nuvem
Em produção, não podemos usar o MongoDB local. O MongoDB Atlas é o serviço de nuvem oficial do MongoDB com um tier gratuito generoso.
Criando um cluster no Atlas:
1. Acesse cloud.mongodb.com e crie uma conta
2. Crie um novo projeto e um cluster (M0 Free é suficiente para começar)
3. Em "Database Access": crie um usuário com senha forte
4. Em "Network Access": adicione 0.0.0.0/0 (aceita conexão de qualquer IP)
— Em produção real, você restringiria ao IP do seu servidor
5. Em "Connect": escolha "Connect your application"
6. Copie a connection string:
mongodb+srv://usuario:senha@cluster.mongodb.net/nomebanco
Esta string vai para a variável de ambiente MONGODB_URL no Railway.
// src/config/database.js
// Configuração de conexão otimizada para produção
const mongoose = require('mongoose');
async function conectar() {
const url = process.env.MONGODB_URL;
if (!url) {
throw new Error('MONGODB_URL não configurada.');
}
try {
await mongoose.connect(url, {
// Timeout para encontrar um servidor disponível no cluster
serverSelectionTimeoutMS: 10000,
// Timeout para operações individuais no banco
socketTimeoutMS: 45000,
});
console.log(`[DB] Conectado: ${mongoose.connection.host}`);
// Eventos de conexão para monitoramento em produção
mongoose.connection.on('disconnected', () => {
console.warn('[DB] Desconectado do MongoDB.');
});
mongoose.connection.on('error', (erro) => {
console.error('[DB] Erro de conexão:', erro.message);
});
// Reconecta automaticamente se a conexão cair
mongoose.connection.on('reconnected', () => {
console.info('[DB] Reconectado ao MongoDB.');
});
} catch (erro) {
console.error('[DB] Falha ao conectar:', erro.message);
throw erro;
}
}
module.exports = { conectar };
Variáveis de ambiente — organização e segurança
Um dos erros mais comuns e perigosos em deploy é vazar secrets. Estas são as regras invioláveis.
# ✅ O que SEMPRE vai no .gitignore
.env
.env.local
.env.production
.env.*.local
# ✅ O que vai no repositório (sem valores reais)
.env.example
# .env.example — template documentado para o time
# Cada desenvolvedor copia este arquivo para .env e preenche os valores
NODE_ENV=development
PORT=3000
MONGODB_URL=mongodb://localhost:27017/meuapp
JWT_SECRET= # gere com: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
JWT_EXPIRA_EM=7d
FRONTEND_URL=http://localhost:5173
# Gerando um JWT_SECRET seguro no terminal
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
# Saída: a4f8c2e9b1d7... (128 caracteres hexadecimais)
# Use este valor como JWT_SECRET — nunca use "segredo123" em produção
Pipeline de CI/CD com GitHub Actions
CI/CD automatiza o processo de teste e deploy. Toda vez que você faz push para main, o GitHub Actions executa os testes e, se passarem, faz o deploy automaticamente. Isso elimina deploys manuais e garante que código com testes falhando nunca vai para produção.
# .github/workflows/deploy.yml
# Este arquivo define o pipeline de CI/CD
name: Testes e Deploy
# Dispara quando há push para a branch main
on:
push:
branches: [main]
# Também roda em Pull Requests — mas sem o step de deploy
pull_request:
branches: [main]
jobs:
# Job 1: roda os testes
testar:
runs-on: ubuntu-latest
name: Testes automatizados
steps:
# Faz checkout do código
- uses: actions/checkout@v4
# Configura Node.js na versão especificada
- name: Configurar Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # cacheia node_modules entre execuções
# Instala dependências com versões exatas do lockfile
- name: Instalar dependências
run: npm ci
# Verifica formatação e lint
- name: Verificar qualidade de código
run: npm run quality
# Roda os testes com relatório de cobertura
- name: Executar testes
run: npm run test:coverage
env:
# Variáveis de ambiente para os testes
# Secrets são configurados no GitHub: Settings → Secrets
MONGODB_TEST_URL: ${{ secrets.MONGODB_TEST_URL }}
JWT_SECRET: test-secret-apenas-para-testes
# Job 2: deploy (só roda se os testes passarem e for push para main)
deploy:
needs: testar # aguarda o job "testar" completar com sucesso
runs-on: ubuntu-latest
# Só faz deploy em push direto para main (não em Pull Requests)
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
name: Deploy para produção
steps:
- uses: actions/checkout@v4
- name: Configurar Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Instalar dependências
run: npm ci
# Deploy no Railway usando o token de autenticação
# RAILWAY_TOKEN é configurado em GitHub Secrets
- name: Deploy no Railway
run: |
npm install -g @railway/cli
railway up --service ${{ secrets.RAILWAY_SERVICE_ID }}
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
Para o front-end no Vercel, o deploy é ainda mais simples — a própria Vercel detecta novos commits no GitHub e faz deploy automaticamente, sem precisar de GitHub Actions.
Monitorando a aplicação em produção
Depois do deploy, você precisa saber se tudo está funcionando. Há três tipos de monitoramento essenciais.
Logs mostram o que está acontecendo em tempo real. O Railway e o Vercel têm dashboards de log integrados.
Uptime monitoring avisa quando sua aplicação cai. O UptimeRobot (gratuito) faz pings no seu /health a cada 5 minutos e manda email se não responder.
Error tracking captura exceções com contexto — qual usuário estava, qual ação executou, qual linha do código falhou. O Sentry tem um plano gratuito excelente.
// Integrando Sentry no back-end Node.js
// npm install @sentry/node
const Sentry = require('@sentry/node');
// Inicializa ANTES de qualquer coisa
Sentry.init({
// DSN vem das configurações do projeto no sentry.io
dsn: process.env.SENTRY_DSN,
// Captura 10% das transações para não estourar a cota gratuita
tracesSampleRate: 0.1,
// Não captura erros em desenvolvimento
enabled: process.env.NODE_ENV === 'production',
});
// No error handler do Express — Sentry precisa vir ANTES do seu handler
app.use(Sentry.Handlers.errorHandler());
app.use(tratadorDeErros); // seu handler customizado
Checklist de deploy
Antes de cada deploy em produção, percorra este checklist mentalmente. Ele foi construído a partir de erros reais — cada item representa algo que já deu errado em algum projeto.
Front-end (React)
─────────────────────────────────────────────────────────
[ ] npm run build executa sem erros
[ ] npm run preview funciona corretamente
[ ] VITE_API_URL aponta para a API de produção
[ ] vercel.json ou netlify.toml com redirect configurado
[ ] Nenhuma URL de localhost hardcoded no código
[ ] Console.log de debug removidos (ou só em dev)
[ ] Lazy loading em todas as páginas grandes
Back-end (Node.js)
─────────────────────────────────────────────────────────
[ ] Todas as configurações leem de process.env
[ ] .env.example atualizado com novas variáveis
[ ] .env nunca commitado (está no .gitignore)
[ ] JWT_SECRET é uma string longa e aleatória
[ ] MONGODB_URL aponta para o Atlas (não para localhost)
[ ] CORS configurado com a URL do front-end de produção
[ ] NODE_ENV=production nas variáveis de ambiente
[ ] Campo "engines" no package.json com versão do Node
[ ] Endpoint /health respondendo 200
[ ] Testes passando (npm test)
Banco de dados (MongoDB Atlas)
─────────────────────────────────────────────────────────
[ ] Cluster criado e rodando
[ ] Usuário do banco criado com senha forte
[ ] Network Access configurado
[ ] Connection string testada localmente antes do deploy
Geral
─────────────────────────────────────────────────────────
[ ] Domínio customizado configurado (opcional)
[ ] HTTPS ativo (Vercel e Railway fazem isso automaticamente)
[ ] Uptime monitor configurado
[ ] Sentry ou similar para error tracking
Domínio customizado
Após o deploy, você recebe uma URL gerada automaticamente como meuapp.vercel.app. Para usar um domínio próprio como meuapp.com.br, o processo é simples.
1. Compre o domínio em um registrador (Registro.br, GoDaddy, Namecheap)
2. No painel da Vercel:
Settings → Domains → Add → "meuapp.com.br"
A Vercel mostrará os registros DNS que você precisa configurar
3. No painel do seu registrador de domínio:
Adicione os registros DNS fornecidos pela Vercel:
Tipo: A Nome: @ Valor: 76.76.21.21
Tipo: CNAME Nome: www Valor: cname.vercel-dns.com
4. Aguarde a propagação DNS (pode levar de minutos a 24 horas)
5. HTTPS é configurado automaticamente pela Vercel via Let's Encrypt
Tarefa para você
Faça o deploy completo da SPA construída no Módulo 6:
# Back-end
# 1. Crie um cluster gratuito no MongoDB Atlas
# e configure a connection string
# 2. Crie uma conta no Railway e faça deploy da API
# Configurar variáveis de ambiente:
# NODE_ENV, PORT, MONGODB_URL, JWT_SECRET, FRONTEND_URL
# 3. Teste o endpoint /health da API em produção:
# curl https://sua-api.railway.app/health
# Front-end
# 4. Configure o arquivo vercel.json para SPA routing
# 5. Crie uma conta na Vercel e importe o repositório
# Configurar variável:
# VITE_API_URL = https://sua-api.railway.app
# 6. Faça login e crie um produto pela URL de produção
# CI/CD
# 7. Configure o GitHub Actions com o workflow do artigo
# Adicione os secrets no GitHub:
# RAILWAY_TOKEN, RAILWAY_SERVICE_ID, MONGODB_TEST_URL
# 8. Faça um commit e observe o pipeline rodando:
# testes → qualidade de código → deploy automático
# Monitoramento
# 9. Crie uma conta gratuita no UptimeRobot
# Monitore o endpoint /health a cada 5 minutos
# 10. Compartilhe a URL da aplicação funcionando em produção!
Conclusão
Neste artigo você aprendeu:
- A diferença entre deploy de front-end (arquivos estáticos) e back-end (processo Node.js)
- Como preparar o projeto React para produção com variáveis de ambiente e code splitting
- Deploy no Vercel com configuração para SPA routing
- Deploy no Netlify como alternativa
- Como preparar a API Node.js para produção com CORS, health check e configuração centralizada
- Deploy no Railway para aplicações Node.js
- MongoDB Atlas para banco de dados em nuvem
- Boas práticas de segurança para variáveis de ambiente
- Pipeline de CI/CD com GitHub Actions — testes automáticos antes de cada deploy
- Monitoramento com logs, uptime monitoring e error tracking
- Checklist completo de deploy para não esquecer nada
- Como configurar domínio customizado
No próximo artigo vamos aprender sobre segurança em aplicações web — as vulnerabilidades mais comuns e como proteger sua aplicação contra elas.
📚 Fontes e Referências
- Vercel — Documentação: https://vercel.com/docs
- Netlify — Documentação: https://docs.netlify.com
- Railway — Documentação: https://docs.railway.app
- MongoDB Atlas: https://www.mongodb.com/atlas
- GitHub Actions — Documentação: https://docs.github.com/en/actions
- Sentry — Node.js SDK: https://docs.sentry.io/platforms/node
- UptimeRobot: https://uptimerobot.com
- Let's Encrypt: https://letsencrypt.org
- The DevOps Handbook — Gene Kim et al. (IT Revolution Press)
- roadmap.sh — DevOps: https://roadmap.sh/devops