Uno degli errori di produzione più utili che mi sia capitato di vedere dopo una distribuzione sembrava quasi banale:
Failed to load script:
/_next/static/chunks/9253.647385b4be0958e4.js
Compariva nello stesso flusso di errori insieme a problemi dell’analisi dati, script pubblicitari, messaggi generici come Script error. e riproduzioni video interrotte. Gran parte di quel flusso era rumore. Questo caso, invece, era diverso: la risorsa che non si era caricata apparteneva alla mia applicazione Next.js. Se il browser non riusciva davvero a recuperarla, una parte della pagina poteva smettere di funzionare.
Da quel log, però, non sapevo perché il chunk fosse fallito. Poteva trattarsi di un problema di rete temporaneo, di un guasto del proxy o della CDN, oppure il file poteva davvero mancare. Un’altra possibilità era che una pagina vecchia stesse ancora chiedendo un chunk della distribuzione precedente dopo che il server aveva già sostituito quella versione.
Quest’ultimo caso è facile da sottovalutare perché la nuova distribuzione può essere perfettamente sana. Tutti i nuovi visitatori ricevono la versione nuova e non vedono problemi, mentre una scheda aperta da ore continua silenziosamente a comportarsi come un client della versione precedente.
Questo articolo riguarda proprio quel vuoto di compatibilità: perché una vecchia scheda Next.js può rompersi dopo una distribuzione, come HTML obsoleto e risorse /_next/static mancanti creano un disallineamento tra versioni, perché una pulizia troppo aggressiva peggiora il problema e come organizzerei distribuzione, conservazione, monitoraggio e recupero affinché un rilascio riuscito non lasci bloccati gli utenti che avevano già l’applicazione aperta.
La prima lezione: non ogni errore di uno script è un problema di distribuzione
Il flusso di errori iniziale conteneva categorie di guasti completamente diverse. Gli script di analisi e pubblicità di terze parti possono essere bloccati da filtri dei contenuti, filtri DNS, funzioni di tutela della privacy, restrizioni regionali, antivirus o dalla rete dell’utente. Una promessa play() di un video può essere interrotta da una successiva chiamata a pause() senza che l’applicazione sia guasta. Un generico Script error. proveniente da un’altra origine spesso fornisce troppo poche informazioni per una diagnosi.
Il mancato caricamento di un chunk Next.js dell’applicazione merita una priorità diversa. La distinzione utile non è «errore JavaScript oppure nessun errore JavaScript», ma qualcosa di più vicino a questo:
risorsa di terze parti non caricata
-> di solito riguarda telemetria o funzionalità facoltative
/_next/static/*.js dell’applicazione non caricato
-> parte del codice applicativo può non essere disponibile
Questa distinzione conta perché un sistema di segnalazione troppo rumoroso può nascondere gli errori che coincidono davvero con pagine non funzionanti. Nel mio caso, l’evento importante era la richiesta a /_next/static/chunks/9253.647385b4be0958e4.js. Il log dimostrava che il caricamento di uno script dell’applicazione era fallito. Non dimostrava che la causa fosse il disallineamento tra distribuzioni.
Mantengo volutamente netta questa soglia probatoria: una causa plausibile non è una causa confermata.
Una scheda aperta è, di fatto, un client di una versione precedente
Il modello mentale che mi ha chiarito il problema è semplice: dopo una distribuzione, più versioni dell’applicazione possono restare vive contemporaneamente.
Supponiamo che la versione A sia online alle 10:00. Un utente apre una pagina e riceve l’HTML insieme al JavaScript necessario per quella rotta. Alle 10:30 entra in servizio la versione B. I nuovi visitatori ricevono B, ma la scheda già aperta non diventa automaticamente B solo perché il server è cambiato.
Quella scheda può contenere ancora:
- l’ambiente di esecuzione JavaScript caricato dalla versione A;
- i riferimenti a rotte e chunk generati dalla versione A;
- dati di navigazione precaricati dalla versione A;
- stato React creato mentre era in esecuzione la versione A;
- moduli suddivisi già scaricati dalla versione A;
- riferimenti a moduli della versione A che non sono ancora stati scaricati.
L’ultimo punto è quello in cui il problema diventa visibile.
Se tutti i chunk che la pagina potrà mai usare sono già nella cache del browser, l’utente può continuare senza accorgersi di nulla. Ma le applicazioni Next.js moderne suddividono il codice. Un cambio di rotta, un’importazione dinamica, una finestra modale, un editor o una funzione usata solo in seguito possono richiedere un altro file JavaScript. Il vecchio ambiente di esecuzione chiede quindi una risorsa il cui URL era valido per la versione A.
Se il server conserva ancora quella risorsa, tutto può continuare a funzionare. Se la distribuzione l’ha eliminata, il vecchio client può ricevere un 404 anche se la versione B è perfettamente sana.
I chunk con hash del contenuto sono pensati per una cache di lunga durata
Next.js assegna intenzionalmente una cache lunga alle risorse realmente immutabili. La documentazione attuale sull’hosting autonomo indica che le risorse immutabili con hash SHA nel nome vengono servite con una politica annuale come questa:
Cache-Control: public, max-age=31536000, immutable
È una scelta sensata perché l’URL cambia quando cambia il contenuto. Un file il cui nome deriva dal contenuto non ha bisogno di essere riconvalidato a ogni richiesta. Se una compilazione successiva produce byte diversi, dovrebbe produrre anche un URL diverso.
La conseguenza importante è facile da perdere di vista: il vecchio URL continua ad avere significato finché un vecchio documento o un vecchio ambiente di esecuzione può ancora farvi riferimento.
Il fatto che il browser possa conservare una risorsa con hash per un anno non aiuta se non aveva mai scaricato proprio quella risorsa prima della distribuzione e il server di origine l’ha già eliminata quando arriva la prima richiesta.
Per questo «i nostri file statici sono immutabili» e «possiamo eliminare subito i file statici della versione precedente» non sono affermazioni equivalenti. L’immutabilità rende sicuro conservare le vecchie risorse; non impedisce ai vecchi client di continuare a richiederle.
L’attuale guida Next.js all’hosting autonomo cita esplicitamente file JavaScript o CSS mancanti come uno dei sintomi del disallineamento tra versioni durante distribuzioni progressive o con più server. È la stessa famiglia di problemi anche quando il disallineamento è tra una vecchia scheda e un server di origine appena aggiornato, anziché tra due server attivi nello stesso momento.
Le versioni possono divergere in modi diversi
«Problema di cache» è una diagnosi troppo vaga per essere utile. Distinguo almeno quattro meccanismi, perché richiedono correzioni diverse.
1. Una vecchia scheda richiede una risorsa mai caricata prima della distribuzione
È il classico caso della scheda rimasta aperta a lungo. Documento e ambiente di esecuzione provengono dalla versione A. La versione B sostituisce i file sul server. Più tardi l’utente compie un’azione che attiva un chunk di A caricato su richiesta. Se la risorsa di A è stata rimossa, la richiesta fallisce.
2. HTML obsoleto punta a chunk che non esistono più
Una CDN, un proxy inverso, un Service Worker, la cache del browser o un livello di hosting statico possono conservare un documento HTML precedente più a lungo del previsto. Quel documento continua a contenere riferimenti della versione A, mentre il server di origine contiene soltanto B.
È particolarmente pericoloso quando all’HTML viene assegnata per errore una lunga politica immutable. JavaScript con hash e HTML non vanno trattati come lo stesso tipo di oggetto in cache. Il chunk può essere immutabile perché il suo URL è versionato dal contenuto. È l’HTML a decidere quali URL dei chunk devono funzionare insieme.
3. Una distribuzione progressiva o con più istanze serve versioni diverse
Immaginiamo due istanze Next.js dietro un bilanciatore di carico. Una è già sulla versione B, l’altra è ancora sulla A. Un documento può arrivare da una versione e una successiva richiesta di navigazione può finire sull’altra. La documentazione Next.js attuale definisce questa situazione disallineamento tra versioni e segnala che può produrre risorse mancanti, incompatibilità delle Server Functions ed errori di navigazione.
La scelta predefinita più sicura è compilare una volta sola e avviare lo stesso artefatto su tutte le istanze che partecipano alla stessa distribuzione. La documentazione Next.js sull’hosting autonomo raccomanda inoltre di usare la stessa compilazione e un identificatore di compilazione coerente in tutti i contenitori, invece di ricompilare ogni replica in modo indipendente.
4. La distribuzione pubblica i file nell’ordine sbagliato
Anche senza schede vecchie, un caricamento non atomico può creare temporaneamente uno stato impossibile:
il nuovo HTML è già visibile
+
i nuovi file dei chunk non sono ancora disponibili
oppure il contrario:
il vecchio HTML è ancora visibile
+
i vecchi file dei chunk sono già stati eliminati
Basta una finestra brevissima. Un utente deve capitarci dentro una sola volta.
Il modello pericoloso è «sostituire tutto ed eliminare il vecchio albero»
Uno script di distribuzione semplice spesso nasce più o meno così:
build
rsync --delete new-output/ production/
restart
È attraente perché la directory di produzione corrisponde sempre esattamente alla compilazione più recente. Ma è ostile ai client rimasti aperti a lungo.
Con risorse statiche dotate di hash, ridurre la directory a una sola versione offre poco vantaggio al browser. I vecchi file non entrano in conflitto con i nuovi perché gli URL sono diversi. Eliminarli serve soprattutto a risparmiare spazio, ma trasforma ogni riferimento ancora valido in un vecchio client in un potenziale 404.
Ora considero i vecchi chunk materiale di compatibilità tra distribuzioni, non spazzatura.
Questo non significa conservare ogni compilazione per sempre. Significa che la pulizia deve essere una politica di conservazione separata, non un effetto collaterale della pubblicazione dell’ultima versione.
Conservare le vecchie risorse aiuta, ma nessuna finestra finita risolve tutto
In un’installazione autonoma è possibile conservare le vecchie risorse /_next/static per un periodo di tolleranza. La durata dipende dal carico. Un sito in cui gli utenti aprono una pagina, leggono per due minuti e se ne vanno ha un profilo di rischio diverso da un’applicazione che resta aperta tutto il giorno.
Un modo utile per ragionare sulla durata minima è:
periodo di conservazione >=
durata prevista dell'HTML obsoleto
+ durata realistica di una scheda aperta a lungo
+ finestra di ripristino della versione precedente
+ margine di propagazione della distribuzione
Non è una garanzia matematica. Una scheda del browser può restare aperta per settimane. Nessun numero finito di ore rende impossibili gli errori dei vecchi client.
Per questo preferisco una difesa a più livelli:
- conservare le risorse immutabili delle versioni precedenti abbastanza a lungo da permettere alle normali sessioni vecchie di continuare;
- rilevare il disallineamento delle versioni affinché il client possa passare alla versione corrente;
- offrire una singola ricarica sicura o un percorso di recupero visibile all’utente quando una risorsa è davvero indisponibile;
- monitorare i chunk dell’applicazione mancanti per regolare la conservazione sulla base di dati reali.
La conservazione evita la maggior parte degli errori. Il recupero gestisce la coda di casi che nessuna finestra finita può eliminare del tutto.
Non eliminate i vecchi chunk usando solo l’età del file
Anche una regola ingenua come «elimina tutti i file più vecchi di sette giorni» può essere sbagliata. La versione corrente potrebbe riutilizzare un vecchio file con hash la cui data di modifica è vecchia semplicemente perché il contenuto non è cambiato.
Un modello di raccolta dei file inutilizzati più robusto deve conoscere le versioni:
- conservare i manifesti o gli inventari delle risorse per ogni versione ancora compresa nella finestra di compatibilità;
- costruire l’unione dei percorsi delle risorse a cui tali versioni fanno riferimento;
- non eliminare mai nulla da questo insieme protetto;
- rimuovere le risorse non più referenziate soltanto dopo un ulteriore periodo di tolleranza.
Se per una piccola installazione è troppa complessità, una directory di risorse statiche volutamente abbondante costa spesso meno che indagare rari errori lato client. I file con hash sono particolarmente adatti perché contenuti identici riutilizzano naturalmente URL stabili o, quantomeno, non possono sovrascrivere contenuti non correlati con lo stesso nome basato su hash.
La regola che eviterei è semplice: non rendere --delete sull’albero condiviso /_next/static parte della stessa operazione che promuove la nuova versione.
Next.js offre una protezione esplicita contro il disallineamento, ma non conserva le vecchie risorse
Le versioni attuali di Next.js supportano deploymentId per proteggere dal disallineamento tra versioni. La configurazione può essere questa:
// next.config.js
const nextConfig = {
deploymentId: process.env.DEPLOYMENT_VERSION,
}
module.exports = nextConfig
Secondo l’attuale documentazione Next.js su deploymentId, la configurazione aggiunge un parametro ?dpl=<deploymentId> agli URL delle risorse statiche gestite dal framework, inserisce informazioni sulla distribuzione nelle richieste di navigazione lato client e fa comunicare al server il proprio identificatore di distribuzione nelle risposte. Quando Next.js rileva una differenza durante la navigazione, può eseguire una navigazione completa invece di proseguire con una transizione lato client basata su dati incompatibili.
?dpl=<deploymentId>
x-deployment-id
x-nextjs-deployment-id
data-dpl-id
È utile, ma non bisogna attribuire a questa funzione capacità che non possiede. La documentazione specifica che Next.js non usa il parametro ?dpl= in ingresso per indirizzare la richiesta verso una determinata versione. Il parametro serve a evitare l’uso di copie memorizzate. Se un server autonomo ha eliminato fisicamente una vecchia risorsa, una stringa di query non può ricreare il file.
Tratto quindi deploymentId come meccanismo di rilevamento del disallineamento e di recupero, non come sostituto di una distribuzione ordinata o della conservazione delle risorse.
Sulle piattaforme che implementano un instradamento consapevole della versione, l’infrastruttura può spingersi oltre. Per esempio, l’attuale documentazione di Vercel sulla protezione dal disallineamento descrive un blocco sulla versione che permette alle richieste gestite dal framework di continuare a raggiungere la distribuzione che aveva servito il client. È una capacità della piattaforma e non qualcosa che presumo esista in una configurazione Nginx o CDN qualsiasi.
L’identificatore di compilazione e quello di distribuzione risolvono problemi collegati ma diversi
Next.js genera anche un identificatore di compilazione durante next build. Se più contenitori devono servire la stessa distribuzione, non dovrebbero trasformarsi silenziosamente in compilazioni diverse perché ogni server ha eseguito autonomamente il proprio passaggio di compilazione.
Un identificatore di compilazione deterministico può essere collegato a un identificatore della versione, per esempio a una revisione Git:
// next.config.js
const nextConfig = {
generateBuildId: async () => process.env.GIT_SHA,
deploymentId: process.env.GIT_SHA,
}
module.exports = nextConfig
Questo esempio è illustrativo e non è copiato dal mio codice di produzione. La regola architetturale importante è che una singola versione logica deve avere un unico artefatto di compilazione coerente e una sola identità di distribuzione in tutte le istanze che la servono.
generateBuildId identifica la compilazione Next.js. deploymentId è documentato specificamente per la protezione dal disallineamento e per evitare copie di cache appartenenti a un’altra distribuzione. Sono concetti collegati, ma trattarne i nomi come sinonimi rende più difficile la diagnosi.
Pubblicarei le risorse prima di spostare il traffico sul nuovo documento
Una sequenza di distribuzione più sicura è volutamente asimmetrica. Le nuove risorse immutabili possono esistere prima che qualcuno vi faccia riferimento. Il nuovo HTML, invece, non deve puntare a risorse che non sono ancora disponibili.
In termini concettuali voglio questo ordine:
1. compilare una sola volta la versione B
2. caricare le risorse /_next/static della versione B
3. verificare che le risorse richieste siano davvero recuperabili
4. avviare o preparare il server e l'ambiente di esecuzione di B
5. verificare lo stato di B
6. spostare atomicamente su B il traffico dei nuovi documenti
7. mantenere disponibili le risorse statiche di A
8. monitorare B
9. eliminare le vecchie risorse in seguito
Se l’applicazione è un’esportazione statica, vale lo stesso principio: prima si caricano le risorse versionate e poi si pubblica l’HTML che le referenzia. Se si tratta di rendering lato server dietro un proxy inverso, si prepara il nuovo server e si cambia il traffico soltanto quando è sano.
Anche il ritorno alla versione precedente deve essere simmetrico. Conservare la directory della versione precedente e le sue risorse statiche permette di tornare indietro senza dover ricostruire vecchi file a posteriori.
Questo non rende sicuro ogni ritorno a una versione precedente. Una migrazione del database o un contratto incompatibile con il servizio di backend possono impedire a una vecchia versione dell’applicazione di funzionare anche se il suo JavaScript esiste ancora. Conservare le risorse statiche risolve il problema della compatibilità statica, non tutti i problemi di compatibilità tra versioni del sistema.
Una directory condivisa di risorse immutabili funziona bene per un hosting autonomo semplice
Per una piccola installazione basata su Nginx, un modello semplice consiste nel separare la versione corrente dell’applicazione da un archivio condiviso delle risorse statiche.
Una struttura di esempio potrebbe essere:
/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/...
Ogni distribuzione aggiunge i nuovi file /_next/static alla directory condivisa senza eliminare quelli delle versioni precedenti ancora conservate. Nginx può servire quel percorso con una politica immutabile:
location ^~ /_next/static/ {
root /srv/app/shared;
add_header Cache-Control "public, max-age=31536000, immutable";
}
Questa configurazione è un esempio, non una descrizione dell’esatta configurazione Nginx che ho usato. Una distribuzione reale deve tenere conto dei permessi, dei tipi MIME, delle varianti compresse, del comportamento della CDN e dell’esatta disposizione dei file prodotti.
Il punto importante è architetturale: il puntatore modificabile alla versione corrente e l’archivio di risorse versionate, che viene soprattutto ampliato, hanno cicli di vita diversi.
L’HTML richiede una politica di cache diversa dai chunk con hash
Il modo più semplice per ricreare il problema è mettere in cache l’HTML come se fosse una risorsa con hash del contenuto.
Per le pagine Next.js renderizzate dinamicamente, il framework usa normalmente risposte non memorizzabili per l’output dinamico specifico dell’utente. Le pagine statiche e ISR seguono politiche diverse e una CDN può legittimamente conservarle. Un’esportazione statica servita da Nginx dipende ancora di più dagli header configurati dall’operatore.
Per questo non applico una sola regola di cache a «tutto il sito». Ragiono per classi di oggetti:
risorsa con hash in /_next/static
max-age lungo
immutable
sicura da conservare
HTML / documento di rotta
deve poter passare alla nuova versione
la politica dipende dal modello di rendering
non deve vivere più delle risorse a cui fa riferimento
RSC / dati di navigazione / dati API
regole separate di compatibilità e freschezza
Se c’è una CDN, dopo una distribuzione può essere necessario invalidare il percorso del nuovo documento, a seconda del progetto della cache. Eliminare i vecchi chunk con hash dalla CDN soltanto perché esiste una nuova versione è spesso controproducente: se anche l’origine li ha eliminati, l’invalidazione toglie l’ultima copia che avrebbe potuto salvare un vecchio client.
La guida Next.js alla cache tramite CDN è utile proprio perché distingue la cache delle pagine dalla politica annuale immutable applicata alle risorse /_next/static.
Il ricaricamento automatico è uno strumento di recupero, non la strategia principale di distribuzione
Una reazione comune al fallimento di un chunk è «basta ricaricare la pagina». Spesso funziona perché una navigazione completa recupera il documento corrente, che fa riferimento alla compilazione corrente.
Ma ricaricare alla cieca a ogni errore di script crea altri problemi:
- il guasto di uno script di terze parti può provocare un ricaricamento inutile;
- un vero guasto del server può creare un ciclo infinito di ricaricamenti;
- un modulo non salvato può perdere i dati inseriti dall’utente;
- lo stato dei componenti React scompare durante una navigazione completa;
- la stessa distribuzione guasta può semplicemente fallire di nuovo.
La documentazione Next.js attuale avverte esplicitamente che la navigazione completa usata per recuperare da un disallineamento può far perdere lo stato dei componenti, come useState, mentre lo stato nell’URL o nell’archiviazione persistente del browser può sopravvivere.
Se aggiungo un recupero lato client, voglio che sia mirato e avvenga una sola volta. Un’implementazione illustrativa potrebbe essere:
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,
)
È volutamente soltanto un esempio. Un’implementazione di produzione dovrebbe considerare anche i chunk dei fogli di stile, le forme note degli errori del framework, i flussi in cui un ricaricamento è distruttivo e il modo in cui azzerare il contrassegno di recupero dopo un caricamento riuscito.
In un editor, durante un acquisto o in un modulo lungo, potrei preferire un avviso come «È disponibile una nuova versione; salva il lavoro e ricarica la pagina» invece di imporre l’aggiornamento.
I dati di monitoraggio devono mostrare se si tratta davvero di disallineamento tra versioni
Un messaggio che dice soltanto «impossibile caricare lo script» non basta. Per distinguere un vecchio chunk eliminato da un problema casuale di rete, mi serve contesto relativo alla distribuzione.
I campi utili includono:
- l’URL della risorsa che non si è caricata;
- l’URL della pagina corrente;
- se la risorsa appartiene all’applicazione;
- l’identificatore di versione o di distribuzione visibile al client;
- browser e sistema operativo;
navigator.onLinecome segnale debole, non come prova della connettività;- tempo trascorso dal caricamento della pagina;
- se l’errore è avvenuto poco dopo una distribuzione;
- se si trattava del primo tentativo di recupero;
- stato HTTP quando è osservabile lato server;
- la versione che stava servendo la richiesta sul server di origine o sul proxy.
A quel punto gli schemi diventano molto più informativi.
Se molti utenti su reti diverse richiedono vecchi URL di chunk con hash e l’origine restituisce 404 subito dopo un rilascio, l’assenza delle vecchie risorse conservate diventa una spiegazione forte. Se un solo utente vede un errore di rete senza risposta HTTP, il disallineamento è molto meno certo. Se il chunk risponde 200 ma con un tipo MIME errato o con una pagina HTML di errore, il problema riguarda l’instradamento o il proxy, non soltanto la conservazione.
Separerei inoltre gli avvisi sui chunk dell’applicazione da quelli sulle risorse di terze parti. È la modifica al monitoraggio sostenuta più direttamente dai miei log iniziali: il segnale significativo era mescolato a molto rumore del browser non collegato a pagine guaste.
Il test di riproduzione è semplice, ma bisogna conservare la vecchia scheda
Questo tipo di errore sfugge facilmente ai normali test di rilascio perché gli sviluppatori tendono a ricaricare la pagina subito dopo una distribuzione. Così eliminano esattamente la condizione che stanno cercando di verificare.
Un test manuale migliore è:
- distribuire la versione A;
- aprire una scheda in condizioni simili alla produzione con la cache del browser attiva;
- visitare soltanto una parte dell’applicazione, così che alcune rotte o funzioni caricate su richiesta restino ancora non caricate;
- lasciare aperta la scheda;
- distribuire la versione B;
- non ricaricare la vecchia scheda;
- attivare una rotta o una funzione dinamica che richieda codice non caricato in precedenza;
- controllare Network e Console;
- verificare che i vecchi URL delle risorse restituiscano ancora 200;
- verificare che il rilevamento del disallineamento esegua una navigazione completa controllata quando serve.
Ripeterei lo stesso test con una CDN davanti, con due istanze server durante una distribuzione progressiva e dopo la scadenza della finestra di conservazione configurata.
Un errore di test meno ovvio è attivare «Disable cache» in DevTools per tutto. Può essere utile in alcune diagnosi, ma modifica il comportamento del browser. Lo scenario della scheda aperta a lungo va provato anche con una cache realistica, perché la cache del browser fa parte del sistema.
Non ogni errore di un chunk si risolve conservando i vecchi file
La conservazione è potente proprio perché risolve un meccanismo specifico. Non deve diventare un’altra spiegazione universale.
Un chunk dell’applicazione può non caricarsi perché:
- la richiesta non ha mai raggiunto il server;
- la connessione è stata interrotta;
- un’estensione del browser l’ha bloccata;
- un nodo della CDN ha avuto un guasto temporaneo;
- Nginx ha instradato male il percorso;
- il server ha restituito un documento HTML di errore invece di JavaScript;
- la compressione o
Content-Encodingsono stati corrotti; - i permessi del file erano errati;
- una distribuzione parziale non ha mai caricato il chunk;
- il file esisteva ma è stato rimosso troppo presto;
- client e server stavano usando distribuzioni incompatibili.
Il codice di risposta e il momento dell’errore contano. Un 404 ripetuto per un vecchio URL con hash del contenuto dopo ogni rilascio racconta una storia molto diversa da ERR_CONNECTION_RESET su una sola rete mobile.
Per questo non riscriverei l’incidente iniziale come «ho dimostrato che l’HTML obsoleto ha rotto il sito». Non l’ho dimostrato. Ho osservato un vero errore di un chunk dell’applicazione e individuato il disallineamento tra versioni come uno dei meccanismi seri che vale la pena prevenire a livello di progetto.
La distribuzione più sicura considera i vecchi client parte della superficie del rilascio
L’errore più profondo è pensare che una distribuzione sostituisca la versione A con B in un unico istante.
Sul server può sembrare così guardando un collegamento simbolico o l’orchestratore dei contenitori. Sulla rete possono però restare vecchi oggetti della CDN. Nei browser, documenti della versione A possono continuare a girare molto dopo l’attivazione di B. Durante un rilascio progressivo entrambe le versioni server possono essere attive. Durante un ritorno alla versione precedente, B può scomparire e A può tornare corrente.
La vera superficie di un rilascio è quindi un intervallo di tempo, non un punto.
Le mie regole di distribuzione per un’applicazione Next.js partono ora da questa idea:
- Compilare una sola volta per ogni versione logica. Non lasciare che le repliche producano in silenzio risultati indipendenti.
- Pubblicare le risorse immutabili prima dei riferimenti che le usano.
- Conservare le vecchie risorse con hash per una finestra di compatibilità scelta deliberatamente.
- Non assegnare all’HTML modificabile la stessa politica di cache dei chunk con hash.
- Usare
deploymentIdquando il modello di distribuzione può produrre disallineamento tra versioni. - Usare la protezione della piattaforma soltanto quando l’hosting offre davvero un instradamento consapevole della versione.
- Rendere il recupero singolo e consapevole dello stato dell’utente.
- Monitorare separatamente gli errori dei chunk dell’applicazione come segnale di produzione.
- Provare una distribuzione lasciando aperta una vecchia scheda.
- Eliminare le vecchie risorse in seguito, non durante la promozione della nuova versione.
La regola che uso oggi
Una compilazione riuscita e una nuova pagina perfettamente funzionante non dimostrano che la distribuzione sia sicura per chi era già nell’applicazione.
La vecchia scheda non è un residuo irrilevante. È un vero client che sta eseguendo una vera versione precedente.
Quando ho iniziato a ragionare così sulle distribuzioni, il problema dei chunk è diventato meno misterioso. Un hash del contenuto assegna alla risorsa un’identità stabile. Una cache di lunga durata rende quell’identità efficiente. Ma la distribuzione deve rispettarla abbastanza a lungo oppure offrire al client un modo controllato per passare alla versione successiva.
Non ho bisogno che ogni vecchia versione resti online per sempre. Ho bisogno che il sistema superi il periodo in cui vecchi client e nuovi server convivono legittimamente.
Questo è il contratto di distribuzione che mi interessa: i nuovi utenti ricevono la nuova versione, quelli già presenti non perdono i file che la loro versione sa ancora richiedere e qualsiasi disallineamento residuo conduce a un percorso di recupero deliberato invece che a una pagina rotta.