Engenharia
Executando Next.js como servidor Node sem Lambda
Implante Next.js como um servidor Node com compilações standalone, systemd, NGINX, streaming, decisões de cache compartilhado e lançamentos seguros.
Next.js pode rodar como um processo Node persistente: compile a aplicação, inicie seu servidor de produção e envie solicitações HTTP. Renderização no servidor, Route Handlers, Server Actions, otimização de imagens e streaming funcionam ali sem um adaptador Lambda. Veja como empacotar esse servidor, mantê-lo em execução e implantar uma segunda instância sem criar problemas de cache ou de lançamento.
O que é executado dentro do processo Node
Uma solicitação chega ao proxy reverso e depois a um servidor Next.js. Esse servidor pode retornar uma página gerada antecipadamente, renderizar uma página para a solicitação atual, executar um Route Handler ou servir uma resposta em cache. Se não houver entrada no cache, pode consultar seu banco de dados ou API. Você não precisa criar um wrapper Express para isso funcionar.
Um processo persistente pode reutilizar pools de banco de dados e conexões HTTP entre solicitações. Você ainda precisa de CPU e memória suficientes para picos de tráfego, uma política de reinício e um procedimento de implantação. Reinício, nova réplica ou cache vazio podem tornar as solicitações mais lentas; executar Node não elimina esses custos.
| Saída | Como executar | O que implantar |
|---|---|---|
| Servidor Node padrão | next build, depois next start. | A compilação, os assets públicos, os metadados dos pacotes e as dependências necessárias de execução. |
| Servidor Node standalone | Defina output: standalone, compile e execute o server.js gerado. | Arquivos rastreados de execução mais public e .next/static. Este é o caminho principal abaixo. |
| Exportação estática | Sirva os arquivos exportados por um servidor HTTP ou armazenamento de objetos. | Somente arquivos estáticos. Recursos de servidor executados por solicitação exigem outro backend. |
The deployment we will build
Browser
|
v
NGINX :443 -- TLS, request limits, streaming passthrough
|
v
Next.js / Node :3001 -- rendering, routes, Server Actions
| |
v v
Database / API Next.js caches
systemd supervises the Node process.
Each release gets its own directory and configuration.Execute a compilação de produção primeiro
Confirme que a aplicação funciona com o servidor de produção antes de configurar uma VM ou um container. Mantenha esses scripts em package.json e instale a partir do lockfile registrado no Git. Instale as dependências de compilação antes de compilar; omitir devDependencies cedo demais pode remover ferramentas necessárias ao compilador.
package.json scripts
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}Verifique o mesmo modo que você irá implantar
Com a saída padrão, os comandos abaixo iniciam o servidor Node completo em loopback. Verifique uma página dinâmica, uma solicitação autenticada e um Route Handler, além da página inicial. Uma sessão bem-sucedida de next dev não testa a pré-renderização de produção, o rastreamento de dependências nem o comportamento do cache de produção.
Standard output: local production check
npm ci
npm run build
npm start -- --hostname 127.0.0.1 --port 3000
# In a second terminal:
curl --fail --show-error -I http://127.0.0.1:3000/Empacote um lançamento standalone
A saída standalone empacota os arquivos rastreados pelo Next.js para o servidor e gera o ponto de entrada server.js. Adicione as opções abaixo ao next.config.mjs existente, preservando as demais configurações da aplicação. Dê um identificador de lançamento a cada compilação e reutilize exatamente sua saída em todas as réplicas desse lançamento.
next.config.mjs
const nextConfig = {
output: "standalone",
deploymentId: process.env.RELEASE_ID,
};
export default nextConfig;Inclua os assets do navegador
A saída standalone não copia public ou .next/static automaticamente. Inclua esses diretórios quando o servidor Node for servir os arquivos. Uma página pode retornar HTML com sucesso enquanto todos os scripts do navegador retornam 404 se essa etapa for esquecida.
Build, package, and start
npm ci
RELEASE_ID=release-001 npm run build
mkdir -p .next/standalone/.next
cp -a .next/static .next/standalone/.next/static
if [ -d public ]; then
cp -a public .next/standalone/public
fi
HOSTNAME=127.0.0.1 PORT=3001 RELEASE_ID=release-001 \
node .next/standalone/server.jsTeste o artefato longe da árvore de fontes
Copie o diretório standalone para um local limpo e inicie server.js nele. Isso detecta dependências que só funcionavam porque o checkout dos fontes estava presente. Em um monorepo, inspecione a estrutura de diretórios gerada: outputFileTracingRoot e outputFileTracingIncludes podem ser necessários para pacotes compartilhados ou arquivos abertos por caminhos dinâmicos.
A separate terminal, using a different port
release_dir=$(mktemp -d)
cp -a .next/standalone/. "$release_dir/"
cd "$release_dir"
HOSTNAME=127.0.0.1 PORT=3002 RELEASE_ID=release-001 node server.jsSepare valores de compilação de segredos de execução
Uma variável de ambiente exclusiva do servidor pode ser lida durante a solicitação. Uma variável NEXT_PUBLIC_ é incorporada ao JavaScript do navegador na compilação. Mudá-la na inicialização do processo não atualiza um bundle já criado. Conteúdo renderizado no servidor durante a compilação também pode capturar os valores disponíveis naquele momento.
| Valor | Defina quando | Consequência para a implantação |
|---|---|---|
| NEXT_PUBLIC_API_URL | Compilando os assets do navegador. | Compile novamente para mudar uma URL incorporada ou exponha uma configuração pública de execução intencional por seu próprio endpoint. |
| DATABASE_URL / credenciais de API | Na inicialização do servidor, quando o código lê os valores dinamicamente. | Mantenha essas credenciais fora das variáveis públicas, do controle de versão e das camadas de imagem. |
| deploymentId | Compilando o lançamento. | Use um identificador para todas as réplicas desse artefato. Uma mudança feita apenas na inicialização não reescreve a compilação. |
| PORT / HOSTNAME | Iniciando server.js. | Use loopback atrás de um proxy no mesmo host; use 0.0.0.0 dentro de um container ou de uma carga de trabalho gerenciada. |
Adicione uma rota de integridade sem cache
Esta rota aguarda uma solicitação antes de ler RELEASE_ID e informa a integridade do processo. Não testa o banco de dados. Se atender ao tráfego exigir banco, adicione um controle separado de disponibilidade usando os timeouts de consulta e conexão do cliente de banco; retorne 503 quando esse caminho necessário falhar. Não torne um serviço opcional de análise uma dependência de disponibilidade.
src/app/api/health/route.js
import { connection } from "next/server";
export async function GET() {
await connection();
return Response.json(
{ ok: true, release: process.env.RELEASE_ID || "unknown" },
{ headers: { "Cache-Control": "no-store" } },
);
}Mantenha o servidor Node em execução com systemd
Em uma VM, execute o servidor empacotado com um usuário dedicado e deixe systemd reiniciá-lo após uma falha. Use um diretório separado por lançamento para que uma implantação não sobrescreva arquivos ainda necessários a um processo ativo. Os comandos abaixo pressupõem um host novo no estilo Debian com Node instalado; verifique command -v node e ajuste ExecStart se o caminho for diferente.
Install the already-built artifact on the target host
sudo useradd --system --home-dir /srv/next --shell /usr/sbin/nologin nextjs
sudo install -d -o nextjs -g nextjs /srv/next/releases/release-001
sudo cp -a .next/standalone/. /srv/next/releases/release-001/
sudo chown -R nextjs:nextjs /srv/next/releases/release-001
sudo install -d -m 700 /etc/next
sudo touch /etc/next/release-001.env
sudo chmod 600 /etc/next/release-001.env
sudoedit /etc/next/release-001.envDefina o ambiente do lançamento
Use esses valores no arquivo de ambiente e adicione os segredos de servidor necessários à aplicação pelo mecanismo de implantação. O limite de heap é um ponto de partida de exemplo, não uma estimativa de capacidade. A memória total do Node também inclui alocações nativas e buffers; por isso deixe margem abaixo do limite total de memória do serviço.
/etc/next/release-001.env
PORT=3001
RELEASE_ID=release-001
NODE_OPTIONS=--max-old-space-size=768Inicie um lançamento identificado
Salve este modelo como /etc/systemd/system/next@.service. O valor %i seleciona o diretório do lançamento e o arquivo de ambiente. Um segundo lançamento pode usar sua própria porta e rodar ao lado do primeiro durante a verificação. Esta unidade supervisiona o processo; não remove do balanceador um processo que falha nos controles de integridade.
/etc/systemd/system/next@.service
[Unit]
Description=Next.js release %i
After=network.target
[Service]
Type=simple
User=nextjs
Group=nextjs
WorkingDirectory=/srv/next/releases/%i
Environment=NODE_ENV=production
Environment=HOSTNAME=127.0.0.1
EnvironmentFile=/etc/next/%i.env
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=30
MemoryMax=1G
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.targetInspecione a inicialização antes de aumentar o tráfego
A resposta de integridade deve identificar release-001. Verifique no journal módulos ausentes, configuração inválida ou permissões dos diretórios de cache. Mantenha disponíveis e graváveis os caminhos de cache Next.js do lançamento; tornar todo o sistema de arquivos somente leitura sem planejar o cache pode impedir regeneração ou otimização de imagens.
Start and inspect
sudo systemctl daemon-reload
sudo systemctl enable --now next@release-001
sudo journalctl -u next@release-001 -n 50 --no-pager
curl --fail --show-error http://127.0.0.1:3001/api/healthColoque NGINX à frente e preserve o streaming
Mantenha a porta Node privada. Na mesma VM, NGINX pode terminar HTTPS e encaminhar para 127.0.0.1:3001. O exemplo pressupõe que o DNS já aponta para o host e que há um certificado válido nos caminhos listados. Substitua app.example.com e valide com nginx -t antes de recarregar.
Deixe o cache do proxy desativado no início e desative o buffering de respostas para que o conteúdo em streaming chegue ao navegador à medida que é produzido. Preserve o host público e o esquema para redirecionamentos e verificações de origem. Essa configuração de cabeçalhos pressupõe que NGINX é a borda pública; se houver um balanceador confiável antes dele, configure explicitamente esse limite de confiança.
/etc/nginx/conf.d/next.conf, inside the http context
upstream next_app {
server 127.0.0.1:3001;
keepalive 32;
}
server {
listen 80;
server_name app.example.com;
return 301 https://app.example.com$request_uri;
}
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 10m;
if ($host != app.example.com) { return 421; }
location / {
proxy_pass http://next_app;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_buffering off;
proxy_cache off;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
}Teste dois blocos pelo nome de host público
Adicione este Route Handler de diagnóstico e compile novamente o lançamento. Solicite-o diretamente e via NGINX com curl --no-buffer. A primeira linha deve chegar antes da segunda. Se ambas chegarem juntas apenas pela rota pública, inspecione buffering em todos os proxies e CDNs entre o navegador e Node.
src/app/api/stream/route.js
import { connection } from "next/server";
export async function GET() {
await connection();
const encoder = new TextEncoder();
const body = new ReadableStream({
async start(controller) {
controller.enqueue(encoder.encode("first chunk\n"));
await new Promise((resolve) => setTimeout(resolve, 1000));
controller.enqueue(encoder.encode("second chunk\n"));
controller.close();
},
});
return new Response(body, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "no-store",
"X-Accel-Buffering": "no",
},
});
}Verifique os limites nas duas camadas
proxy_read_timeout é um timeout de inatividade entre leituras do upstream. Streams de longa duração precisam de timeouts adequados e, quando apropriado, sinais periódicos de atividade. A aplicação também tem limites de upload: aumentar o limite do corpo no NGINX não muda os limites de Server Actions do Next.js. Ajuste o limite específico atingido, em vez de liberar todos globalmente.
Check proxy configuration and streaming
sudo nginx -t
sudo systemctl reload nginx
curl --fail --no-buffer http://127.0.0.1:3001/api/stream
curl --fail --no-buffer https://app.example.com/api/streamSaiba qual cache você está mudando
Não existe uma única configuração de cache do Next.js que cubra todas as camadas. Uma página de produto desatualizada pode vir de cache da aplicação, rota gerada, resposta de CDN ou estado de navegação do navegador. Identifique a camada antes de mudar TTLs ou adicionar Redis.
| Camada | O que armazena | O que verificar |
|---|---|---|
| Assets de compilação | JavaScript, CSS, e outros arquivos sob .next/static. | Implante assets da mesma compilação e preserve os arquivos necessários aos navegadores que usam o lançamento anterior. |
| Cache de dados / ISR | Dados de fetch em cache e saída regenerada das rotas no modelo de cache relevante. | O armazenamento local deve permitir escrita. Múltiplas réplicas exigem planejamento de armazenamento e coordenação de invalidação. |
| Cache Components | Valores criados por use cache e diretivas relacionadas. | O padrão é memória local do processo. Uma diretiva remota exige um handler externo configurado para compartilhar dados. |
| Otimização de imagens | Variantes geradas por next/image. | Acompanhe CPU, disco e latência da primeira solicitação. Um handler de cache de dados não compartilha automaticamente os arquivos de imagens otimizadas. |
| Proxy reverso / CDN | Respostas HTTP permitidas pela política de cache da borda. | Respeite Cache-Control e a variação de respostas. Páginas autenticadas e respostas públicas compartilhadas exigem políticas diferentes. |
As duas APIs de handlers de cache são diferentes
Para o cache incremental do servidor usado por ISR e dados em cache, o Next.js expõe cacheHandler, no singular. Ao configurar armazenamento compartilhado para esse modelo, cacheMaxMemorySize: 0 pode desativar a camada de memória de cada processo. Seu handler precisa implementar o armazenamento e o comportamento de tags exigidos.
Cache Components usa cacheHandlers, no plural. Um handler remoto configurado pode fornecer o armazenamento de use cache: remote; sem ele, a diretiva sozinha não provisiona Redis nem cria um cache compartilhado. Coordene o estado das tags e dos valores, incluindo refreshTags quando a API do handler exigir. Escolha a API adequada à versão instalada do Next.js e ao modelo de cache.
No Next.js 16.2 ou posterior, imagens otimizadas podem usar cacheHandler por meio de images.customCacheHandler: true. Esse handler deve suportar entradas IMAGE, incluindo seus dados binários e validade. Configure e teste isso explicitamente se quiser compartilhar variantes de imagem entre réplicas.
Não armazene todo o HTML em cache na borda
A navegação Next.js pode solicitar payloads de React Server Components e também HTML. Uma CDN deve preservar a variação de solicitações e as chaves de cache exigidas pelo framework. Uma regra genérica de armazenar tudo em cache pode misturar tipos de resposta ou expor conteúdo personalizado. Comece pelos cabeçalhos de cache do framework e pela orientação oficial de CDN, depois teste separadamente solicitações autenticadas e anônimas.
Adicione réplicas sem duplicar problemas
Execute o mesmo artefato em todas as réplicas de um lançamento, mas dê a cada processo sua própria identidade de execução e caminhos locais graváveis. Armazene uploads duradouros fora do diretório do lançamento. As sessões devem funcionar em qualquer réplica que atenda à próxima solicitação, usando cookies verificados ou armazenamento de sessões compartilhado, em vez de um objeto local do processo.
Planeje as conexões ao banco de dados para todos os processos. Quatro processos com pools de até dez conexões podem usar quarenta conexões; manter quatro processos antigos ativos durante uma implantação pode elevar esse número a oitenta, antes de contar workers e conexões administrativas. Defina os limites dos pools de acordo com a capacidade do banco e o maior número temporário de réplicas.
Meça CPU, atraso do loop de eventos, memória total do processo, latência de solicitações e erros sob uma carga representativa. Um processo Node persistente pode lidar com I/O concorrente, mas JavaScript que exige muita CPU pode atrasar solicitações sem relação com ele. Mova trabalho pesado em segundo plano para um worker e escale com base nos gargalos medidos. Aumentar o limite de heap do V8 não corrige um gargalo de CPU.
Implante um novo lançamento enquanto o anterior ainda está em uso
Inicie release-002 em seu próprio diretório e na porta 3002 enquanto release-001 ainda atende ao tráfego em 3001. Execute controles de integridade e da aplicação em 3002. Depois mude o upstream do NGINX para 3002, valide a configuração e recarregue. Mantenha o processo antigo disponível enquanto as solicitações existentes terminam e você verifica o novo lançamento.
Um navegador ainda pode manter JavaScript e dados pré-carregados de release-001. deploymentId permite ao Next.js detectar uma divergência e iniciar uma navegação completa, que pode descartar estado não salvo dos componentes. O parâmetro de consulta dpl não faz o Next.js encaminhar solicitações a um lançamento anterior. Preserve os assets antigos e use roteamento por versão se os clientes precisarem continuar se comunicando com esse lançamento.
| Preocupação | O que preservar | Falha a testar |
|---|---|---|
| Assets do navegador | Os arquivos referenciados pelas novas páginas e pelas páginas antigas ainda abertas. | Abra uma página antes da promoção e depois carregue um componente importado sob demanda. |
| Server Actions | Um único artefato e configuração compatível de criptografia de ações em suas réplicas. | Envie um formulário carregado antes da promoção depois que o tráfego tiver mudado. |
| Esquema do banco de dados | Compatibilidade com os dois lançamentos durante a sobreposição e a reversão. | Execute o lançamento anterior no esquema migrado antes de declarar que a reversão está disponível. |
| Solicitações em andamento | Um período medido para concluir solicitações em andamento antes de encerrar o processo antigo. | Mude o tráfego durante uma resposta lenta ou streaming e verifique sua conclusão. |
Mantenha as chaves de Server Actions consistentes
A criptografia das closures de Server Actions usa uma chave definida na compilação. Reutilizar um artefato preserva sua chave gerada entre réplicas. Se gerenciar NEXT_SERVER_ACTIONS_ENCRYPTION_KEY explicitamente, injete a mesma chave AES válida codificada em base64 nas compilações que a exigem e proteja-a como um segredo. Compartilhar a chave não torna intercambiáveis os IDs de ações de compilações diferentes.
Conclua as solicitações em andamento, pare o processo e preserve um caminho de reversão
Retire a instância antiga do novo tráfego antes de enviar SIGTERM. Deixe o trabalho em andamento terminar dentro do prazo de encerramento; aumente o limite de 30 segundos do exemplo se as medições das solicitações exigirem. Callbacks de after() não são uma fila durável de tarefas: trabalho que precisa sobreviver a uma falha deve ficar em uma fila persistente com tratamento de novas tentativas.
Se o novo lançamento falhar, devolva o tráfego ao processo que funcionava e preserve os logs da falha. Migrações de banco de dados e efeitos em sistemas externos podem tornar essa reversão insegura; por isso a compatibilidade do esquema precisa ser testada antes da promoção.
Implante o mesmo modelo de servidor no Adios
No Adios, declare comando de compilação, comando de inicialização standalone, porta de escuta e caminho de integridade em adios.yaml. O gateway público encaminha à carga de trabalho Node, que deve escutar em 0.0.0.0. A configuração systemd e NGINX da VM não é necessária dentro dessa carga gerenciada.
Defina um RELEASE_ID exclusivo em build.env e env para cada implantação, para que a compilação e o processo em execução identifiquem o mesmo lançamento. Inclua devDependencies na compilação mesmo se NODE_ENV for production. Este exemplo começa com uma réplica. Aumentar a quantidade não configura automaticamente cache compartilhado, sessões compartilhadas nem capacidade do banco de dados.
adios.yaml
name: web
region: de
replicas: 1
build_cmd: |
npm ci --include=dev
npm run build
mkdir -p .next/standalone/.next
cp -a .next/static .next/standalone/.next/static
if [ -d public ]; then cp -a public .next/standalone/public; fi
start_cmd: node .next/standalone/server.js
build:
env:
RELEASE_ID: release-001
env:
NODE_ENV: production
HOSTNAME: 0.0.0.0
PORT: "3000"
RELEASE_ID: release-001
runtime:
name: node@24
port: 3000
health_path: /api/health
memory_mb: 1024Verifique estas falhas antes de aumentar o tráfego
Execute as verificações no lançamento de produção empacotado e em seu nome de host final. Mantenha o ID do lançamento nos logs da aplicação para identificar se um erro afeta uma réplica, uma compilação ou o serviço inteiro.
- —Um artefato limpo inicia sem acesso ao checkout dos fontes.
- —Controles de integridade, solicitações autenticadas, assets, tratamento de imagens e streaming funcionam por HTTPS.
- —O processo se recupera após um reinício e uma dependência com falha produz a resposta de disponibilidade prevista.
- —Uma aba antiga do navegador funciona corretamente durante a promoção, incluindo o envio de formulários.
- —As réplicas retornam os mesmos dados após a invalidação, e há capacidade disponível para conexões no banco.
- —O lançamento anterior e um esquema compatível continuam disponíveis para reversão.
| Sintoma | Onde investigar primeiro | Verificação |
|---|---|---|
| HTML funciona; os scripts ou estilos retornam 404 | Arquivos .next/static ausentes ou lançamentos misturados. | Verifique uma URL de script no HTML real e confirme que o artefato do lançamento contém o arquivo. |
| A URL pública retorna 502 | Endereço de escuta, portas diferentes ou um processo encerrado por falha. | Solicite a rota de integridade diretamente na porta Node e inspecione os logs do proxy e do processo. |
| O streaming chega de uma vez. | Buffering no proxy reverso ou CDN. | Compare o endpoint de dois blocos diretamente e pelo nome de host público. |
| O navegador ainda usa uma URL de API antiga | Um valor NEXT_PUBLIC_ incorporado ao bundle. | Inspecione o código compilado para o navegador e compile novamente com a configuração pública prevista. |
| Só algumas solicitações mostram dados desatualizados | Caches de réplicas independentes ou um cache de borda. | Verifique cada réplica diretamente e verifique os valores armazenados e a invalidação da tag. |
| Server Actions falham após um lançamento | Divergência entre compilações, chaves de criptografia diferentes ou cabeçalhos de origem. | Compare IDs de lançamento, o artefato usado por cada réplica e o encaminhamento Host/Origin. |
| O processo é encerrado sob carga | A memória total excede o limite do host ou do container. | Verifique RSS, carga de otimização de imagens, crescimento do cache e motivo de encerramento informado pelo supervisor. |