Implantar o Next.js em um servidor Linux standalone (AWS EC2, DigitalOcean Droplet, Hetzner, etc.) usando next build && next start. Tudo o que a Vercel cuida automaticamente para você - CDN, SSL, escalonamento, implantações de pré-visualização, variáveis de ambiente - agora é sua responsabilidade. Este guia detalha cada peça.
Quando usar isso: Sua empresa exige auto-hospedagem, você precisa controlar o ambiente do servidor, você está implantando em uma VPC sem acesso à internet pública ou deseja custos mensais previsíveis em vez de cobrança baseada no uso.
# Clone seu repositóriogit clone https://github.com/your-org/your-app.git /home/nextjs/appcd /home/nextjs/app# Instale as dependências de produçãonpm ci# Compile para produçãoNODE_ENV=production npx next build
Após a conclusão da compilação, o diretório .next/ contém:
.next/├── cache/ # Cache ISR, cache de otimização de imagem, cache de build├── server/ # Pacotes do lado do servidor (App Router pages, API routes)│ ├── app/ # Páginas compiladas do App Router│ ├── chunks/ # Chunks compartilhados do servidor│ └── pages/ # Páginas compiladas do Pages Router (se houver)├── static/ # Pacotes JS/CSS do lado do cliente (com fingerprint)│ └── chunks/ # Pacotes do cliente com code-split├── BUILD_ID # Identificador único da compilação├── build-manifest.json # Mapeia rotas para pacotes do cliente└── trace # Dados de rastreamento da compilação
Inicie o servidor de produção para verificar:
NODE_ENV=production npx next start -p 3000# Visite http://<ip-do-servidor>:3000 para verificar, depois Ctrl+C
# .env.production# Apenas do lado do servidor (não exposto ao navegador)DATABASE_URL="postgresql://user:pass@db-host:5432/mydb"NEXTAUTH_SECRET="your-secret-key-here"NEXTAUTH_URL="https://myapp.com"# Lado do cliente (incorporado no pacote JS no momento da COMPILAÇÃO)NEXT_PUBLIC_API_URL="https://api.myapp.com"NEXT_PUBLIC_POSTHOG_KEY="phc_xxxxxxxxxxxx"
O prefixo NEXT_PUBLIC_ é crucial para entender:
Prefixo
Disponível Onde
Resolvido Quando
Mudança Requer
NEXT_PUBLIC_
Servidor + Cliente (navegador)
Tempo de compilação (incorporado no pacote JS)
Recompilação
Sem prefixo
Apenas Servidor
Tempo de execução (lido de process.env)
Reinício
Para variáveis de ambiente gerenciadas pelo PM2, use um ecosystem.config.js:
O PM2 mantém seu processo Node.js ativo, reinicia-o em caso de falha e sobrevive a reinicializações do servidor.
# Instale o PM2 globalmentenpm install -g pm2
Crie um ecosystem.config.js pronto para produção com modo cluster:
// ecosystem.config.jsmodule.exports = { apps: [ { name: "myapp", script: "node_modules/.bin/next", args: "start -p 3000", cwd: "/home/nextjs/app", instances: "max", // Use todos os núcleos de CPU disponíveis exec_mode: "cluster", // Modo cluster para balanceamento de carga max_memory_restart: "512M", // Reinicie se a memória exceder 512MB env: { NODE_ENV: "production", PORT: 3000, }, // Logging error_file: "/home/nextjs/logs/err.log", out_file: "/home/nextjs/logs/out.log", log_date_format: "YYYY-MM-DD HH:mm:ss Z", merge_logs: true, }, ],};
Inicie e persista:
# Crie o diretório de logsmkdir -p /home/nextjs/logs# Inicie o aplicativopm2 start ecosystem.config.js# Salve a lista de processos (para que o PM2 saiba o que reiniciar após a reinicialização)pm2 save# Gere o script de inicialização (execute o comando que ele exibe como root)pm2 startup# Copie e cole o comando gerado, por exemplo:# sudo env PATH=$PATH:/home/nextjs/.nvm/versions/node/v20.x.x/bin pm2 startup systemd -u nextjs --hp /home/nextjs# Verifique se os processos estão em execuçãopm2 ls
# Instale o certbotsudo apt install certbot python3-certbot-nginx -y# Obtenha e instale o certificado (o plugin Nginx configura o SSL automaticamente)sudo certbot --nginx -d myapp.com -d www.myapp.com# Verifique se a renovação automática está configuradasudo certbot renew --dry-run# O Certbot instala um timer systemd automaticamente; verifique-o:sudo systemctl list-timers | grep certbot
Crie um script de implantação que puxe o código mais recente, compile e recarregue graciosamente:
#!/bin/bash# /home/nextjs/deploy.shset -euo pipefailAPP_DIR="/home/nextjs/app"LOG_FILE="/home/nextjs/logs/deploy-$(date +%Y%m%d-%H%M%S).log"echo "=== Implantação iniciada em $(date) ===" | tee "$LOG_FILE"cd "$APP_DIR"# Puxe o código mais recenteecho "Puxando o código mais recente..." | tee -a "$LOG_FILE"git pull origin main 2>&1 | tee -a "$LOG_FILE"# Instale as dependências (ci para instalações limpas)echo "Instalando dependências..." | tee -a "$LOG_FILE"npm ci 2>&1 | tee -a "$LOG_FILE"# Compile o aplicativoecho "Compilando..." | tee -a "$LOG_FILE"NODE_ENV=production npx next build 2>&1 | tee -a "$LOG_FILE"# Recarregue graciosamente os processos PM2 (sem tempo de inatividade)echo "Recarregando processos PM2..." | tee -a "$LOG_FILE"pm2 reload myapp 2>&1 | tee -a "$LOG_FILE"echo "=== Implantação concluída em $(date) ===" | tee -a "$LOG_FILE"
Por que pm2 reload em vez de pm2 restart:
reload -- Inicia novos processos de worker primeiro, espera que eles aceitem conexões e, em seguida, encerra graciosamente os workers antigos. Sem tempo de inatividade.
restart -- Mata todos os workers imediatamente e, em seguida, inicia novos. Breve tempo de inatividade enquanto os novos processos inicializam.
Configure seu balanceador de carga ou serviço de monitoramento para consultar https://myapp.com/api/health a cada 30 segundos. Uma resposta 200 com "status": "ok" significa que o servidor está íntegro.
Configure a rotação de logs do PM2 para evitar que os logs consumam todo o espaço em disco:
# Instale o módulo de rotação de logspm2 install pm2-logrotate# Configure as configurações de rotaçãopm2 set pm2-logrotate:max_size 50M # Rotacione quando o log atingir 50MBpm2 set pm2-logrotate:retain 30 # Mantenha 30 arquivos rotacionadospm2 set pm2-logrotate:compress true # Comprima logs antigospm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
Para enviar logs para um serviço centralizado:
Opção A: CloudWatch (AWS)
# Instale o agente do CloudWatchsudo apt install amazon-cloudwatch-agent -y# Configure-o para monitorar os arquivos de log do PM2# /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
A Regeneração Incremental de Estado (ISR) funciona imediatamente em um único servidor porque as páginas regeneradas são gravadas em .next/cache/ no disco local. Quando uma página é revalidada, o Next.js:
Serve a página desatualizada imediatamente
Regenera a página em segundo plano
Grava a nova página em .next/cache/
Serve a nova página na próxima solicitação
O problema com múltiplos servidores: Se você escalar para 2+ servidores atrás de um balanceador de carga, cada servidor terá seu próprio .next/cache/. O Servidor A pode ter uma página atualizada enquanto o Servidor B ainda serve uma desatualizada. Os usuários veem conteúdo inconsistente.
Soluções:
Sessões fixas -- Direcione usuários para o mesmo servidor via afinidade de sessão do ALB. Mais simples, mas reduz a eficácia do balanceamento de carga.
Montagem NFS compartilhada -- Monte .next/cache/ de um volume EFS. Todos os servidores compartilham o mesmo cache. Adiciona latência, mas garante consistência.
Manipulador de cache personalizado -- Use incrementalCacheHandlerPath em next.config.ts para apontar para um cache com base em Redis ou S3:
// next.config.tsimport type { NextConfig } from "next";const nextConfig: NextConfig = { cacheHandler: "./cache-handler.ts", cacheMaxMemorySize: 0, // Desabilita o cache em memória, usa apenas armazenamento externo};export default nextConfig;
// cache-handler.tsimport { CacheHandler } from "next/dist/server/lib/incremental-cache";import { createClient } from "redis";const client = createClient({ url: process.env.REDIS_URL });client.connect();export default class RedisCacheHandler extends CacheHandler { async get(key: string) { const data = await client.get(key); return data ? JSON.parse(data) : null; } async set(key: string, data: unknown, ctx: { revalidate?: number }) { const ttl = ctx.revalidate ?? 60; await client.set(key, JSON.stringify(data), { EX: ttl }); } async revalidateTag(tag: string) { // Escaneia por chaves com esta tag e as exclui const keys = await client.keys(`*:${tag}:*`); if (keys.length > 0) { await client.del(keys); } }}
Definir output: "standalone" em next.config.ts instrui o processo de compilação a rastrear as importações do seu aplicativo e agrupar apenas os node_modules necessários em .next/standalone/:
// next.config.tsimport type { NextConfig } from "next";const nextConfig: NextConfig = { output: "standalone",};export default nextConfig;
Após a compilação, o diretório .next/standalone/ contém:
.next/standalone/├── node_modules/ # Apenas as dependências que seu aplicativo realmente usa (~50MB)├── server.js # Ponto de entrada mínimo do servidor Node.js├── package.json└── .next/ └── server/ # Pacotes do servidor compilados
Você deve copiar manualmente os ativos estáticos:
# Após compilar com output: "standalone"cp -r public .next/standalone/publiccp -r .next/static .next/standalone/.next/static
Em seguida, inicie com:
cd .next/standaloneNODE_ENV=production node server.js
O PM2 reinicia automaticamente os processos que falharam. Configure limites de memória para capturar vazamentos de memória:
# Defina o limite de memória (reinicia se excedido)pm2 start ecosystem.config.js # max_memory_restart já definido na configuração# Monitore em tempo realpm2 monit
Endpoint de verificação de integridade para ALB:
Configure a verificação de integridade do grupo de destino do AWS ALB:
Caminho:/api/health
Intervalo: 30 segundos
Limite de integridade: 2 sucessos consecutivos
Limite de não integridade: 3 falhas consecutivas
Tempo limite: 10 segundos
Gerenciamento de espaço em disco:
O diretório .next/cache/ pode crescer sem limites, especialmente com ISR e otimização de imagem:
# Tarefa cron para limpar o cache ISR com mais de 7 dias# Adicione ao crontab -e0 3 * * * find /home/nextjs/app/.next/cache -type f -mtime +7 -delete 2>/dev/null
Ajuste de memória do Node.js:
Se seu aplicativo processa grandes cargas úteis ou conjuntos de dados, aumente o limite do heap V8:
// ecosystem.config.jsmodule.exports = { apps: [ { name: "myapp", script: "node_modules/.bin/next", args: "start -p 3000", node_args: "--max-old-space-size=1024", // Limite de heap de 1 GB max_memory_restart: "1200M", // Limite de reinício do PM2 acima do limite V8 // ... resto da configuração }, ],};
Esquecer de copiar public/ e .next/static/ com a saída standalone. O modo output: "standalone" agrupa apenas o código do servidor. Ativos estáticos (public/, .next/static/) devem ser copiados manualmente para .next/standalone/, ou o Nginx servirá 404s para todos os arquivos CSS, JS e de imagem.
Executar next start como root. Se o processo Node.js for comprometido, o invasor terá acesso root. Sempre crie um usuário nextjs dedicado com permissões mínimas. O PM2 é executado como esse usuário, e o Nginx (que precisa das portas 80/443) é executado como seu próprio usuário www-data.
Não definir NODE_ENV=production. O Next.js pula otimizações críticas em modo de desenvolvimento: sem minificação, sem eliminação de código morto, páginas de erro detalhadas com source maps expostos. Sempre defina NODE_ENV=production em sua configuração PM2 ou ambiente shell antes de compilar e iniciar.
Expor a porta 3000 diretamente para a internet. Nunca deixe os usuários acessarem o processo Node.js diretamente. O Nginx fornece terminação TLS, limitação de taxa, cabeçalhos de segurança, compressão gzip e proteção contra ataques slowloris. Seu grupo de segurança deve permitir apenas a porta 3000 de 127.0.0.1.
Cache ISR crescendo sem limites. Em sites de alto tráfego com muitas páginas dinâmicas (por exemplo, /product/[id] com 100 mil produtos), .next/cache/fetch-cache/ e .next/cache/images/ podem preencher o disco. Monitore o uso do disco e configure um trabalho cron para limpar entradas de cache antigas.
Faltando cabeçalhos Upgrade no Nginx. Sem proxy_set_header Upgrade $http_upgrade e proxy_set_header Connection "upgrade", as conexões WebSocket falham silenciosamente. Isso afeta o streaming de respostas do Server Actions, o streaming do React Server Components e o HMR em modo de desenvolvimento. A conexão parece funcionar, mas os dados nunca chegam.
Renovação do Certbot não automatizada. Os certificados Let's Encrypt expiram a cada 90 dias. Embora o certbot configure um timer systemd por padrão, verifique se ele está ativo: sudo systemctl list-timers | grep certbot. Se o timer estiver faltando, adicione 0 0 1 * * certbot renew --quiet ao crontab do root.
Variáveis NEXT_PUBLIC_ incorporadas no tempo de compilação. Desenvolvedores que migram da Vercel estão acostumados a alterar variáveis de ambiente em um painel e tê-las em vigor na próxima solicitação. Em um servidor standalone, as variáveis NEXT_PUBLIC_ são incorporadas no pacote JavaScript durante next build. Alterá-las requer uma recompilação e redespacho completos, não apenas um reinício do PM2. Variáveis apenas do lado do servidor (sem o prefixo NEXT_PUBLIC_) entram em vigor após um reinício.
Compilação falhando com OOM em instâncias pequenas.next build pode consumir mais de 2 GB de RAM em aplicativos grandes. Se você estiver compilando em um t3.micro (1 GB de RAM), adicione espaço de troca ou compile em uma instância maior e copie o diretório .next/ para lá.
Esquecer de persistir .next/cache/ entre implantações. Se o seu script de implantação executar rm -rf .next antes de compilar, você perderá o cache de compilação e o cache ISR. As compilações demoram mais e todas as páginas ISR precisam ser regeneradas. Em vez disso, remova apenas .next/server/ e .next/static/ se necessário, preservando .next/cache/.
A ISR (Regeneração Incremental de Estado) ainda funciona em um servidor standalone?
Sim. A ISR funciona imediatamente em um único servidor porque as páginas regeneradas são gravadas em .next/cache/ no disco local. A única complicação são configurações com múltiplos servidores onde cada servidor tem seu próprio cache. Nesse caso, use sessões fixas, uma montagem NFS compartilhada (AWS EFS) ou um manipulador de cache personalizado com base em Redis ou S3.
Como faço implantações de pré-visualização sem Vercel?
Você tem várias opções: (1) Execute um processo PM2 separado por branch em uma porta diferente, com o Nginx roteando por subdomínio (pr-123.preview.myapp.com). (2) Use Coolify ou Dokku, que fornecem implantações de pré-visualização automáticas. (3) Pule as implantações de pré-visualização e confie em ambientes de staging. A maioria das equipes escolhe a opção 3, a menos que tenham um engenheiro de DevOps dedicado.
E a otimização de imagem? next/image ainda funciona?
Sim, next/image funciona em um servidor standalone. A diferença é que a otimização de imagem (redimensionamento, conversão de formato para WebP/AVIF) é executada na CPU do seu servidor em vez da rede de borda da Vercel. Para sites de alto tráfego, isso pode ser intensivo em CPU. Mitigação: coloque uma CDN (CloudFront, Cloudflare) na frente do seu servidor para armazenar em cache imagens otimizadas, ou use a prop loader para descarregar para um serviço como Cloudinary ou Imgix.
Como faço para reverter uma implantação ruim?
Como você está implantando via git pull, reverta selecionando o commit anterior e recompilando:
cd /home/nextjs/appgit log --oneline -5 # Encontre o último commit bomgit checkout <commit-hash> # Selecione esse commitnpm ci && NODE_ENV=production npx next buildpm2 reload myapp
Para reverter mais rapidamente, mantenha o diretório de compilação .next/ anterior como backup antes de cada implantação.
Posso usar Server Actions em um servidor standalone?
Sim. Server Actions funcionam de forma idêntica em um servidor standalone. Eles são executados como requisições POST para o mesmo servidor Node.js. A única diferença em relação à Vercel é que eles são executados em um processo Node.js de longa duração em vez de uma função serverless, portanto, esteja ciente de vazamentos de memória em processos de longa duração.
Preciso de um balanceador de carga para um único servidor?
Não. Uma única instância EC2 com Nginx como proxy reverso é suficiente. Você só precisa de um balanceador de carga (AWS ALB/NLB) ao escalar para múltiplos servidores. No entanto, mesmo com um servidor, colocá-lo atrás de um ALB oferece verificações de integridade, terminação SSL fácil via ACM (sem necessidade de certbot) e um caminho de migração mais simples quando você escalar mais tarde.
Quanto custa isso em comparação com a Vercel?
Uma instância EC2 t3.medium (2 vCPUs, 4 GB RAM) custa aproximadamente $30/mês com uma instância reservada ou $34/mês sob demanda. Isso pode lidar com tráfego moderado que custaria $100+ na Vercel Pro. No entanto, você está pagando com seu tempo por operações, monitoramento e patches de segurança. Para equipes pequenas, o serviço gerenciado da Vercel é frequentemente mais barato quando você considera o tempo de engenharia.
Devo usar o modo de saída standalone ou o padrão?
Use output: "standalone" ao implantar em contêineres Docker ou quando quiser o menor artefato de implantação possível (~50 MB). Use a saída padrão ao implantar com o diretório node_modules/ completo e quiser implantações mais simples (apenas git pull && npm ci && next build && pm2 reload). Standalone adiciona uma etapa manual de cópia de public/ e .next/static/.
Como lidar com múltiplos ambientes (staging, production)?
Use arquivos .env.staging e .env.production separados. O Next.js carrega .env.production automaticamente quando NODE_ENV=production. Para staging, defina NODE_ENV=staging com uma estratégia de carregamento de env personalizada, ou use arquivos de ecossistema PM2 com blocos de env diferentes por ambiente:
O Middleware é executado da mesma forma que na Vercel?
A API é idêntica, mas o tempo de execução é diferente. Na Vercel, o Middleware é executado em isolados de borda V8 (API Web limitada). Em um servidor standalone, o Middleware é executado no tempo de execução completo do Node.js, o que significa que você tem acesso a mais APIs do Node.js, mas perde o benefício da localização de borda. Se o seu Middleware for sensível à latência (por exemplo, redirecionamentos de geolocalização), considere colocar uma CDN na frente do seu servidor.
Como configurar um pipeline CI/CD para isso?
Use GitHub Actions (ou sua ferramenta de CI) para SSH no servidor e executar o script de implantação:
E se minha compilação demorar muito e causar tempo de inatividade?
A compilação é executada enquanto a versão antiga ainda está servindo tráfego (o PM2 mantém os processos antigos ativos até pm2 reload). Não há tempo de inatividade durante a compilação em si. O único risco é se a compilação consumir tanta CPU/RAM que degrade o aplicativo em execução. Soluções: (1) Compile em um servidor de CI separado e copie o diretório .next/ via rsync. (2) Use uma instância maior durante as compilações. (3) Adicione espaço de troca.