Um dos erros de produção mais úteis que encontrei depois de uma implantação parecia quase banal:
Failed to load script:
/_next/static/chunks/9253.647385b4be0958e4.js
Ele apareceu no mesmo fluxo que falhas de ferramentas de análise, scripts de publicidade, mensagens genéricas como Script error. e reprodução de vídeo interrompida. A maior parte daquele fluxo era ruído. Este caso era diferente: o recurso que falhou pertencia à minha própria aplicação Next.js. Se o navegador realmente não conseguisse carregá-lo, uma parte da página poderia deixar de funcionar.
O que eu não sabia por aquele registro era por que o chunk havia falhado. Poderia ter ocorrido um problema temporário de rede, uma falha no proxy ou na CDN, ou o arquivo poderia realmente estar ausente. Outra possibilidade era uma página antiga continuar pedindo um chunk da implantação anterior depois que o servidor já havia substituído aquela versão.
Esse último cenário é fácil de subestimar porque a nova implantação pode estar perfeitamente saudável. Visitantes novos recebem a versão atual e tudo funciona, enquanto uma aba aberta há horas continua silenciosamente sendo cliente da versão anterior.
Este artigo trata exatamente dessa lacuna de compatibilidade: por que abas antigas do Next.js podem quebrar depois de uma implantação, como HTML desatualizado e recursos /_next/static ausentes criam desencontro de versões, por que uma limpeza agressiva piora o problema e como eu organizaria implantação, retenção, monitoramento e recuperação para que uma versão publicada com sucesso não abandone usuários que já estavam com a aplicação aberta.
A primeira lição: nem toda falha de script é um problema de implantação
O fluxo de erros original misturava classes de falha completamente diferentes. Scripts de análise e publicidade de terceiros podem ser bloqueados por filtros de conteúdo, filtros DNS, recursos de privacidade, restrições regionais, antivírus ou pela rede do usuário. Uma promessa de play() de um vídeo pode ser interrompida por uma chamada posterior a pause() sem que a aplicação esteja quebrada. Uma mensagem genérica entre origens, como Script error., muitas vezes traz informação insuficiente para qualquer diagnóstico.
A falha de um chunk da própria aplicação Next.js merece outra prioridade. A divisão útil não é “erro de JavaScript contra ausência de erro de JavaScript”, mas algo mais próximo disto:
falha de recurso de terceiro
-> normalmente afeta telemetria ou funcionalidade opcional
falha de /_next/static/*.js da própria aplicação
-> parte do código da aplicação pode ficar indisponível
Essa distinção importa porque um sistema de relatórios muito ruidoso pode esconder as falhas que realmente coincidem com páginas quebradas. No meu caso, o evento importante era a solicitação para /_next/static/chunks/9253.647385b4be0958e4.js. O registro comprovava que um script da própria aplicação não carregou. Não comprovava que o desencontro entre implantações era a causa.
Mantenho essa fronteira de evidência deliberadamente clara: uma causa plausível ainda não é uma causa confirmada.
Uma aba aberta do navegador é, na prática, um cliente de uma versão anterior
O modelo mental que mudou a forma como enxergo o problema é simples: depois de uma implantação, mais de uma versão da aplicação pode continuar viva ao mesmo tempo.
Suponha que a versão A esteja ativa às 10:00. Um usuário abre uma página e recebe o HTML junto com o JavaScript necessário para aquela rota. Às 10:30, a versão B a substitui. Visitantes novos recebem B. Mas a aba que já estava aberta não se transforma automaticamente em B só porque o servidor mudou.
Essa aba ainda pode conter:
- o ambiente de execução JavaScript carregado da versão A;
- referências de rotas e chunks geradas pela versão A;
- dados de navegação pré-carregados da versão A;
- estado do React criado durante a execução da versão A;
- módulos de código dividido da versão A que já foram baixados;
- referências a módulos da versão A que ainda não foram baixados.
É no último ponto que a falha costuma aparecer.
Se todos os chunks de que a página poderá precisar já estiverem no cache do navegador, o usuário pode continuar sem perceber nada. Mas aplicações Next.js modernas dividem o código. Uma mudança de rota, uma importação dinâmica, uma janela modal, um editor ou uma funcionalidade usada mais tarde pode exigir outro arquivo JavaScript. O ambiente antigo então solicita um recurso cujo URL era válido para a versão A.
Se o servidor ainda tiver esse recurso, tudo pode continuar funcionando. Se a implantação o tiver removido, o cliente antigo pode receber 404 mesmo com a versão B perfeitamente saudável.
Chunks com hash de conteúdo foram feitos para ficar muito tempo em cache
O Next.js dá deliberadamente semântica de cache longa a recursos realmente imutáveis. A documentação atual de hospedagem própria informa que recursos imutáveis com hashes SHA no nome do arquivo são servidos com uma política de um ano, como:
Cache-Control: public, max-age=31536000, immutable
Essa política faz sentido porque o URL muda quando o conteúdo muda. Um arquivo cujo nome deriva do conteúdo não precisa ser revalidado a cada solicitação. Se uma compilação posterior produzir bytes diferentes, deverá produzir também outro URL de recurso.
A consequência importante é fácil de ignorar: o URL antigo continua tendo significado enquanto algum documento ou ambiente de execução antigo ainda puder referenciá-lo.
O fato de o navegador poder manter um recurso com hash por um ano não ajuda se aquele arquivo específico nunca foi baixado antes da implantação e o servidor de origem já o apagou quando a primeira solicitação acontece.
Por isso, “nossos arquivos estáticos são imutáveis” e “podemos apagar imediatamente os arquivos estáticos da versão anterior” não são afirmações equivalentes. A imutabilidade torna seguro guardar recursos antigos; ela não faz clientes antigos pararem de pedi-los.
O guia atual do Next.js para hospedagem própria cita explicitamente arquivos JavaScript ou CSS ausentes como um dos sintomas de desencontro de versões em implantações graduais ou com vários servidores. É a mesma família de problema mesmo quando o desencontro ocorre entre uma aba antiga e um servidor de origem recém-atualizado, em vez de entre dois servidores ativos ao mesmo tempo.
As versões podem se separar de várias maneiras
“Problema de cache” é vago demais para ser um diagnóstico útil. Eu separo pelo menos quatro mecanismos, porque eles exigem correções diferentes.
1. Uma aba antiga solicita um recurso que nunca havia carregado antes da implantação
Este é o caso clássico de uma aba aberta por muito tempo. O documento e o ambiente de execução vieram da versão A. A versão B substitui os arquivos no servidor. Mais tarde, o usuário executa uma ação que aciona um chunk da A carregado sob demanda. Se o recurso da A foi removido, a solicitação falha.
2. HTML desatualizado aponta para chunks que já não existem
Uma CDN, um proxy reverso, um Service Worker, o cache do navegador ou uma camada de hospedagem estática podem guardar um documento HTML antigo por mais tempo que o esperado. Esse HTML ainda pode conter referências da versão A enquanto o servidor de origem contém apenas B.
Isso é especialmente perigoso quando o HTML recebe por engano uma política longa de immutable. JavaScript com hash e HTML não devem ser tratados como o mesmo tipo de objeto de cache. O chunk pode ser imutável porque seu URL é versionado pelo conteúdo. É o HTML que determina quais URLs de chunks pertencem ao mesmo conjunto.
3. Uma implantação gradual ou com várias instâncias serve versões misturadas
Imagine duas instâncias Next.js atrás de um balanceador de carga. Uma já está na versão B; a outra ainda está na A. Um documento pode vir de uma versão e uma solicitação de navegação posterior cair na outra. A documentação atual do Next.js chama isso de desencontro de versões e observa que pode causar recursos ausentes, incompatibilidades de Server Functions e falhas de navegação.
O padrão mais seguro é compilar uma única vez e executar o mesmo artefato de compilação em todas as instâncias que participam da mesma implantação. A documentação de hospedagem própria do Next.js também recomenda usar a mesma compilação e um identificador de compilação consistente entre contêineres, em vez de recompilar cada réplica separadamente.
4. A própria implantação publica os arquivos na ordem errada
Mesmo sem abas antigas, um envio não atômico pode criar temporariamente um estado impossível:
o HTML novo já está visível
+
os novos arquivos de chunk ainda não estão disponíveis
ou o inverso:
o HTML antigo ainda está visível
+
os arquivos de chunk antigos já foram apagados
Uma janela curta basta. O usuário só precisa cair nela uma vez.
O padrão perigoso é “substituir tudo e apagar a árvore antiga”
Um script simples de implantação costuma começar com algo assim:
build
rsync --delete new-output/ production/
restart
É atraente porque a pasta de produção sempre coincide exatamente com a compilação mais nova. Também é hostil a clientes que permanecem abertos por muito tempo.
Com recursos estáticos que têm hash, limpar a pasta até restar uma única versão oferece pouco benefício ao navegador. Arquivos antigos não entram em conflito com os novos porque seus URLs são diferentes. Apagá-los economiza principalmente espaço em disco, mas transforma toda referência ainda válida em um cliente antigo em um possível 404.
Hoje penso em chunks antigos como material de compatibilidade da implantação, não como lixo.
Isso não significa guardar todas as compilações para sempre. Significa que a limpeza deve ser uma política separada de retenção, não um efeito colateral da publicação da versão mais recente.
A retenção ajuda, mas nenhuma janela finita é uma solução completa
Uma instalação própria pode manter recursos antigos de /_next/static durante um período de tolerância. A duração exata depende do uso. Um site em que a pessoa abre uma página, lê por dois minutos e sai tem um perfil de risco diferente de uma aplicação que fica aberta durante todo o dia de trabalho.
Uma forma útil de pensar na janela mínima é:
janela de retenção >=
duração esperada do HTML desatualizado
+ duração realista de uma aba aberta por muito tempo
+ janela de reversão
+ margem de propagação da implantação
Isso não é uma garantia matemática. Uma aba do navegador pode permanecer aberta por semanas. Nenhum número finito de horas torna impossível uma falha em clientes antigos.
Por isso prefiro uma proteção em camadas:
- manter recursos imutáveis de versões anteriores por tempo suficiente para que sessões antigas normais continuem funcionando;
- detectar o desencontro de versões para que o cliente possa avançar para a versão atual;
- oferecer uma única recarga segura ou um caminho de recuperação visível ao usuário quando um recurso estiver realmente indisponível;
- monitorar chunks da própria aplicação que estejam ausentes para ajustar a retenção com dados reais.
A camada de retenção evita a maioria das falhas. A camada de recuperação cuida dos casos restantes que nenhuma janela finita consegue eliminar por completo.
Não remova chunks antigos usando apenas a idade do arquivo
Uma limpeza ingênua como “apagar todo arquivo com mais de sete dias” também pode estar errada. A versão atual pode reutilizar um arquivo antigo com hash cuja data de modificação é antiga simplesmente porque o conteúdo não mudou.
Um modelo de coleta de arquivos não utilizados mais forte precisa conhecer as versões:
- manter os manifestos ou inventários de recursos de cada versão que ainda esteja dentro da janela de compatibilidade;
- construir a união dos caminhos de recursos referenciados por essas versões;
- nunca apagar nada desse conjunto protegido;
- remover recursos sem referência somente depois de um período adicional de tolerância.
Se isso for complexo demais para uma implantação pequena, uma pasta de recursos estáticos intencionalmente generosa costuma custar menos do que investigar raras falhas em clientes. Arquivos com hash são especialmente adequados a esse modelo porque conteúdo idêntico reutiliza naturalmente URLs estáveis ou, no mínimo, não pode sobrescrever conteúdo diferente com o mesmo nome baseado em hash.
A regra que eu evitaria é simples: não incluir --delete na árvore compartilhada /_next/static na mesma operação que promove a nova versão.
O Next.js já tem proteção explícita contra desencontro de versões, mas ela não armazena recursos antigos
O Next.js atual oferece deploymentId para proteção contra desencontro de versões. A configuração pode ser assim:
// next.config.js
const nextConfig = {
deploymentId: process.env.DEPLOYMENT_VERSION,
}
module.exports = nextConfig
Segundo a documentação atual do Next.js sobre deploymentId, essa configuração acrescenta um parâmetro ?dpl=<deploymentId> aos URLs de recursos estáticos gerenciados pelo framework, inclui informações da implantação nas solicitações de navegação do cliente e faz o servidor indicar seu identificador de implantação nas respostas. Quando o Next.js detecta uma diferença durante a navegação, pode executar uma navegação completa em vez de continuar uma transição no cliente com dados incompatíveis.
?dpl=<deploymentId>
x-deployment-id
x-nextjs-deployment-id
data-dpl-id
Isso é útil, mas é importante não atribuir ao recurso mais poder do que ele tem. A documentação afirma explicitamente que o Next.js não usa o parâmetro ?dpl= recebido para encaminhar a solicitação a uma versão específica. Ele serve para evitar cópias antigas em cache. Se um servidor de origem hospedado por conta própria apagou fisicamente um recurso antigo, um parâmetro de consulta não recria o arquivo.
Por isso trato deploymentId como mecanismo de detecção do desencontro e de recuperação, não como substituto de uma implantação bem organizada ou da retenção de recursos.
Em plataformas que implementam roteamento por versão, a infraestrutura pode ir além. Por exemplo, a documentação atual da Vercel sobre proteção contra desencontro descreve a fixação da versão, que permite que solicitações gerenciadas pelo framework continuem sendo atendidas pela implantação que serviu o cliente. Essa é uma capacidade da plataforma, não algo que eu presuma existir em uma configuração qualquer de Nginx ou CDN.
O identificador de compilação e o de implantação resolvem problemas relacionados, mas diferentes
O Next.js também gera um identificador de compilação durante next build. Se vários contêineres devem servir a mesma implantação, eles não deveriam se transformar silenciosamente em compilações diferentes porque cada servidor executou sua própria etapa de compilação.
Um identificador de compilação determinístico pode ser ligado a um identificador da versão, como uma revisão do Git:
// next.config.js
const nextConfig = {
generateBuildId: async () => process.env.GIT_SHA,
deploymentId: process.env.GIT_SHA,
}
module.exports = nextConfig
Este exemplo é ilustrativo e não foi copiado do meu código de produção. A regra arquitetural importante é que uma versão lógica tenha um único artefato de compilação coerente e uma identidade de implantação única em todas as instâncias que a servem.
generateBuildId identifica a compilação do Next.js. deploymentId é documentado especificamente para proteção contra desencontro de versões e para evitar cache pertencente a outra implantação. Os conceitos se relacionam, mas tratá-los como sinônimos torna a depuração mais difícil.
Eu publicaria os recursos antes de transferir o tráfego para o novo documento
Uma sequência de implantação mais segura é deliberadamente assimétrica. Novos recursos imutáveis podem existir antes de alguém apontar para eles. O novo HTML, por outro lado, não deve referenciar recursos que ainda não estão disponíveis.
Conceitualmente, quero esta ordem:
1. compilar a versão B uma única vez
2. enviar os recursos /_next/static da versão B
3. verificar se os recursos necessários realmente podem ser recuperados
4. iniciar ou preparar o servidor e o ambiente de execução da versão B
5. verificar a saúde da versão B
6. transferir atomicamente para B o tráfego de novos documentos
7. manter disponíveis os recursos estáticos da versão A
8. monitorar a versão B
9. remover os recursos antigos depois
Se a aplicação for uma exportação estática, vale o mesmo princípio: primeiro enviar os recursos versionados e depois publicar o HTML que os referencia. Se houver renderização no servidor atrás de um proxy reverso, preparar o novo servidor e só mudar o tráfego depois que ele estiver saudável.
A reversão deve ser simétrica. Manter a pasta da versão anterior e seus recursos estáticos torna possível voltar a ela sem tentar reconstruir arquivos antigos de memória.
Isso não torna toda reversão segura. Uma migração de banco de dados ou um contrato incompatível no serviço de backend pode impedir que uma versão antiga da aplicação funcione mesmo com o JavaScript ainda presente. A retenção de recursos estáticos resolve o problema de compatibilidade estática, não todos os problemas de compatibilidade entre versões do sistema.
Uma pasta compartilhada de recursos imutáveis funciona bem em hospedagem própria simples
Para uma implantação pequena baseada em Nginx, um padrão direto é separar a versão atual da aplicação de um armazenamento compartilhado de recursos estáticos.
Uma estrutura ilustrativa poderia ser:
/srv/app/releases/2026-08-13-a/
/srv/app/releases/2026-08-13-b/
/srv/app/current -> /srv/app/releases/2026-08-13-b/
/srv/app/shared/_next/static/...
Cada implantação adiciona seus novos arquivos /_next/static à pasta compartilhada sem apagar os arquivos das versões anteriores que ainda estão sendo mantidas. O Nginx pode servir esse caminho com uma política imutável:
location ^~ /_next/static/ {
root /srv/app/shared;
add_header Cache-Control "public, max-age=31536000, immutable";
}
Essa configuração é apenas um exemplo, não uma afirmação sobre a configuração exata de Nginx que usei. Uma implantação real precisa considerar permissões, tipos MIME, variantes de compressão, comportamento da CDN e a disposição exata dos arquivos gerados.
O ponto importante é arquitetural: o ponteiro mutável para a versão atual e o armazenamento de recursos versionados, que é principalmente acrescido, têm ciclos de vida diferentes.
O HTML precisa de uma política de cache diferente da dos chunks com hash
A forma mais simples de recriar o problema é colocar HTML em cache como se fosse um recurso identificado por hash de conteúdo.
Para páginas Next.js renderizadas dinamicamente, o framework normalmente usa respostas não armazenáveis para conteúdo dinâmico específico do usuário. Páginas estáticas e ISR seguem políticas diferentes, e uma CDN pode legitimamente armazená-las. Uma exportação estática servida pelo Nginx depende ainda mais dos cabeçalhos configurados pelo operador.
Por isso não aplico uma única regra de cache a “todo o site”. Penso em classes de objetos:
recurso com hash em /_next/static
max-age longo
immutable
seguro para manter
HTML / documento de rota
precisa conseguir avançar para a nova versão
a política depende do modelo de renderização
não deve viver mais do que os recursos que referencia
RSC / dados de navegação / dados da API
regras próprias de compatibilidade e atualidade
Se houver uma CDN, dependendo do desenho do cache pode ser necessário invalidar o caminho do novo documento depois da implantação. Invalidar chunks antigos com hash só porque existe uma nova versão geralmente é contraproducente: se a origem também os apagou, a invalidação remove a última cópia que poderia salvar um cliente antigo.
O guia do Next.js sobre cache em CDN é útil aqui porque separa o cache de páginas da política anual immutable usada nos recursos /_next/static.
Recarregamento automático é uma ferramenta de recuperação, não a principal estratégia de implantação
Uma resposta comum a uma falha de chunk é “basta recarregar a página”. Isso costuma funcionar porque uma navegação completa busca o documento atual, que aponta para a compilação atual.
Mas recarregar cegamente a cada erro de script cria novos problemas:
- uma falha em script de terceiro pode provocar recarga inútil;
- uma falha real do servidor pode criar um ciclo infinito de recargas;
- um formulário não salvo pode perder os dados inseridos;
- o estado dos componentes React desaparece em uma navegação completa;
- a mesma implantação quebrada pode simplesmente falhar de novo.
A própria documentação atual do Next.js alerta que a navegação completa usada na recuperação do desencontro pode perder estado de componentes, como useState, enquanto o estado guardado no URL ou em armazenamento persistente do navegador pode sobreviver.
Se eu adicionar recuperação no cliente, quero que seja específica e aconteça apenas uma vez. Uma implementação ilustrativa poderia ser:
const RECOVERY_KEY = 'next-chunk-recovery-attempted'
function isOwnNextAsset(url: string) {
try {
const parsed = new URL(url, window.location.href)
return (
parsed.origin === window.location.origin &&
parsed.pathname.startsWith('/_next/static/')
)
} catch {
return false
}
}
window.addEventListener(
'error',
(event) => {
const target = event.target
if (!(target instanceof HTMLScriptElement)) return
if (!isOwnNextAsset(target.src)) return
reportChunkFailure({
page: window.location.href,
asset: target.src,
})
if (sessionStorage.getItem(RECOVERY_KEY)) return
sessionStorage.setItem(RECOVERY_KEY, '1')
window.location.reload()
},
true,
)
Este é intencionalmente apenas um exemplo. Uma implementação de produção também deve considerar chunks de folhas de estilo, formatos conhecidos de erro do framework, fluxos em que recarregar destrói o trabalho do usuário e a forma de limpar o marcador de recuperação depois de um carregamento saudável.
Em um editor, processo de compra ou formulário longo, eu talvez prefira mostrar um aviso como “Há uma nova versão disponível; salve seu trabalho e recarregue a página” em vez de forçar a atualização.
Os dados de monitoramento devem mostrar se isto é realmente desencontro de versões
Uma mensagem que diz apenas “falha ao carregar script” não basta. Para distinguir um chunk antigo apagado de uma falha aleatória de rede, preciso de contexto sobre a implantação.
Campos úteis incluem:
- o URL do recurso que falhou;
- o URL da página atual;
- se o recurso pertence à própria aplicação;
- o identificador de versão ou implantação visível ao cliente;
- navegador e sistema operacional;
navigator.onLinecomo sinal fraco, não como prova de conectividade;- tempo desde o carregamento da página;
- se o erro ocorreu pouco depois de uma implantação;
- se esta foi a primeira tentativa de recuperação;
- estado HTTP quando puder ser observado no servidor;
- a versão que estava atendendo a solicitação no servidor de origem ou proxy.
Com isso, os padrões ficam muito mais informativos.
Se muitos usuários em redes diferentes solicitam URLs antigos de chunks com hash e a origem responde 404 logo após uma nova versão, a ausência dos recursos antigos mantidos passa a ser uma explicação forte. Se apenas um usuário vê uma falha de rede sem resposta HTTP, o desencontro é muito menos certo. Se o chunk responde 200 com tipo MIME incorreto ou com uma página HTML de erro, o problema está no roteamento ou na configuração do proxy, não apenas na retenção.
Eu também criaria alertas separados para falhas de chunks da própria aplicação e falhas de recursos de terceiros. Essa é a mudança de monitoramento mais diretamente apoiada pelos meus registros originais: o sinal relevante estava misturado com muito ruído do navegador sem relação com páginas quebradas.
O teste de reprodução é simples, mas precisa preservar a aba antiga
Esse tipo de erro passa facilmente pelos testes normais de uma versão porque os engenheiros tendem a atualizar a página imediatamente depois da implantação. Isso elimina exatamente a condição que se quer testar.
Um teste manual melhor é:
- implantar a versão A;
- abrir uma aba em condições parecidas com produção, com o cache do navegador habilitado;
- visitar apenas parte da aplicação para deixar algumas rotas ou funcionalidades sob demanda sem carregar;
- manter essa aba aberta;
- implantar a versão B;
- não atualizar a aba antiga;
- acionar uma rota ou funcionalidade dinâmica que precise de código ainda não carregado;
- examinar Network e Console;
- verificar se os URLs antigos de recursos ainda retornam 200;
- verificar se a detecção de desencontro executa uma navegação completa controlada quando necessário.
Eu repetiria o mesmo teste com uma CDN à frente, com duas instâncias do servidor durante uma implantação gradual e depois do fim da janela de retenção configurada.
Um erro sutil de teste é habilitar “Disable cache” no DevTools para tudo. Isso pode ajudar em alguns diagnósticos, mas muda o comportamento do navegador. O cenário de uma aba aberta por muito tempo também deve ser testado com cache realista, porque o cache do navegador faz parte do sistema.
Nem toda falha de chunk se resolve mantendo arquivos antigos
A retenção é poderosa justamente porque resolve um mecanismo específico. Ela não deve virar outra explicação universal.
Um chunk da própria aplicação pode falhar porque:
- a solicitação nunca chegou ao servidor;
- a conexão foi interrompida;
- uma extensão do navegador a bloqueou;
- um nó da CDN sofreu uma falha temporária;
- o Nginx encaminhou o caminho incorretamente;
- o servidor retornou um documento HTML de erro em vez de JavaScript;
- a compressão ou o
Content-Encodingforam corrompidos; - as permissões do arquivo estavam erradas;
- uma implantação parcial nunca enviou o chunk;
- o arquivo existia, mas foi removido cedo demais;
- cliente e servidor estavam em versões incompatíveis.
O código de resposta e o momento importam. Um 404 repetido para um URL antigo com hash de conteúdo depois de cada versão conta uma história muito diferente de ERR_CONNECTION_RESET em uma única rede móvel.
Por isso eu não reescreveria meu incidente original como “provei que HTML desatualizado quebrou o site”. Não provei. Observei uma falha real de um chunk da própria aplicação e identifiquei o desencontro de versões como um mecanismo sério que vale a pena eliminar por projeto.
A implantação mais segura trata clientes antigos como parte da superfície da versão
O erro mais profundo é imaginar que uma implantação substitui a versão A pela B em um único instante.
No servidor, é isso que um link simbólico ou o orquestrador de contêineres pode sugerir. Na rede, objetos antigos da CDN podem continuar existindo. Nos navegadores, documentos da versão A podem continuar executando muito depois de B entrar no ar. Durante uma implantação gradual, as duas versões do servidor podem estar ativas. Em uma reversão, B pode desaparecer e A voltar a ser atual.
Portanto, a verdadeira superfície de uma versão é um intervalo de tempo, não um ponto.
Minhas regras de implantação para uma aplicação Next.js agora partem dessa ideia:
- Compilar uma única vez por versão lógica. Não deixar réplicas produzirem silenciosamente saídas independentes.
- Publicar recursos imutáveis antes de publicar referências a eles.
- Manter recursos antigos com hash durante uma janela de compatibilidade escolhida conscientemente.
- Não dar ao HTML mutável a mesma política de cache usada nos chunks com hash.
- Usar
deploymentIdquando o modelo de implantação puder gerar desencontro de versões. - Usar proteção da plataforma somente quando a hospedagem realmente oferecer roteamento ciente da versão.
- Fazer a recuperação uma única vez e respeitar o estado do usuário.
- Monitorar falhas de chunks da própria aplicação como um sinal de produção separado.
- Testar uma implantação mantendo uma aba antiga aberta.
- Remover recursos antigos depois, não durante a promoção da nova versão.
A regra que uso hoje
Uma compilação bem-sucedida e uma página nova funcionando não provam que a implantação seja segura para usuários que já estavam ali.
A aba antiga não é um resto irrelevante. É um cliente real executando uma versão anterior real.
Quando comecei a pensar nas implantações dessa forma, o problema dos chunks ficou menos misterioso. Um hash de conteúdo dá identidade estável ao recurso. Cache de longa duração torna essa identidade eficiente. Mas a implantação precisa respeitá-la por tempo suficiente ou oferecer ao cliente uma forma controlada de avançar.
Não preciso manter todas as versões antigas para sempre. Preciso que o sistema atravesse o período em que clientes antigos e servidores novos coexistem legitimamente.
Esse é o contrato de implantação que importa para mim: usuários novos recebem a nova versão, usuários antigos não perdem os arquivos que sua versão atual ainda sabe solicitar e qualquer incompatibilidade restante termina em um caminho deliberado de recuperação, não em uma página quebrada.