Een van de nuttigste productiefouten die ik na een uitrol zag, leek op het eerste gezicht bijna onbeduidend:
Failed to load script:
/_next/static/chunks/9253.647385b4be0958e4.js
De melding stond in dezelfde stroom als fouten van analysetools, advertentiescripts, algemene Script error.-meldingen en onderbroken videoweergave. Het grootste deel daarvan was ruis. Dit geval was anders: de mislukte bron hoorde bij mijn eigen Next.js-toepassing. Als de browser het bestand werkelijk niet kon laden, kon een deel van de pagina ophouden te werken.
Wat ik uit dat logboek niet wist, was waarom de chunk mislukte. Er kon een tijdelijke netwerkstoring zijn geweest, een proxy of CDN kon falen, of het bestand kon echt ontbreken. Een andere mogelijkheid was dat een oudere pagina nog om een chunk van de vorige uitrol vroeg nadat de server die versie al had vervangen.
Dat laatste scenario wordt gemakkelijk onderschat, omdat de nieuwe uitrol zelf volledig gezond kan zijn. Nieuwe bezoekers krijgen de nieuwe versie en merken niets, terwijl een tabblad dat al uren openstaat ongemerkt een client van de vorige versie blijft.
Dit artikel gaat over precies die compatibiliteitskloof: waarom oude Next.js-tabbladen na een uitrol kunnen stuklopen, hoe verouderde HTML en ontbrekende /_next/static-bronnen tot versieverschillen leiden, waarom te agressief opruimen het probleem groter maakt en hoe ik uitrol, bewaring, monitoring en herstel zou inrichten zodat een geslaagde release bestaande gebruikers niet met een kapotte pagina achterlaat.
De eerste les: niet elke scriptfout is een uitrolfout
De oorspronkelijke foutstroom bevatte totaal verschillende soorten storingen. Externe analyse- en advertentiescripts kunnen worden geblokkeerd door inhoudsfilters, DNS-filters, privacyfuncties, regionale filtering, antivirussoftware of het netwerk van de gebruiker. Een play()-belofte van een video kan door een latere pause() worden onderbroken zonder dat de toepassing defect is. Een algemene herkomstoverschrijdende Script error. bevat vaak te weinig informatie om iets te kunnen diagnosticeren.
Een mislukte chunk van de eigen Next.js-toepassing verdient een andere prioriteit. De nuttige scheidslijn is niet ‘JavaScript-fout of geen JavaScript-fout’, maar eerder:
externe bron mislukt
-> meestal telemetrie of een optionele functie aangetast
eigen /_next/static/*.js mislukt
-> toepassingscode kan niet beschikbaar zijn
Dat onderscheid is belangrijk, omdat een luidruchtige foutmelder de problemen kan verbergen die werkelijk samengaan met defecte pagina’s. In mijn geval was het belangrijke voorval de aanvraag voor /_next/static/chunks/9253.647385b4be0958e4.js. Het logboek bewees dat een eigen script niet werd geladen. Het bewees niet dat een versieverschil door de uitrol de oorzaak was.
Ik houd die bewijsgrens bewust scherp: een aannemelijke oorzaak is nog geen bevestigde oorzaak.
Een open browsertabblad is feitelijk een client van een oudere release
Het mentale model dat het probleem voor mij veranderde is eenvoudig: na een uitrol kunnen meerdere versies van de toepassing tegelijk blijven bestaan.
Stel dat release A om 10:00 actief is. Een gebruiker opent een pagina en ontvangt de HTML plus het JavaScript voor die route. Om 10:30 vervangt release B haar. Nieuwe bezoekers krijgen B, maar het al geopende tabblad wordt niet automatisch B alleen omdat de server is veranderd.
Dat tabblad kan nog bevatten:
- de JavaScript-runtime die uit release A is geladen;
- route- en chunkverwijzingen die door release A zijn gegenereerd;
- vooraf geladen navigatiegegevens uit release A;
- React-status die onder release A is aangemaakt;
- opgesplitste modules uit A die al zijn gedownload;
- verwijzingen naar modules uit A die nog niet zijn gedownload.
Bij dat laatste punt wordt de fout zichtbaar.
Als elke chunk die de pagina ooit nodig zal hebben al in de browsercache staat, kan de gebruiker doorgaan zonder iets te merken. Moderne Next.js-toepassingen splitsen hun code echter op. Een routewissel, dynamische import, dialoogvenster, editor of functie die later wordt gebruikt, kan nog een JavaScript-bestand nodig hebben. De oude runtime vraagt dan een bron op via een URL die voor release A geldig was.
Als de server die bron nog heeft, kan alles normaal blijven werken. Heeft de uitrol het bestand verwijderd, dan kan de oude client een 404 krijgen terwijl release B zelf volledig gezond is.
Chunks met een inhoudshash zijn bedoeld voor langdurige caching
Next.js geeft werkelijk onveranderlijke bronnen bewust een lange cacheduur. Volgens de huidige documentatie voor zelfhosting worden onveranderlijke bronnen met SHA-hashes in de bestandsnaam bijvoorbeeld met een beleid van één jaar aangeboden:
Cache-Control: public, max-age=31536000, immutable
Dat is logisch omdat de URL verandert wanneer de inhoud verandert. Een bestandsnaam die uit de inhoud is afgeleid hoeft niet bij iedere aanvraag opnieuw te worden gevalideerd. Als een latere compilatie andere bytes oplevert, hoort daar een andere bron-URL bij.
Een belangrijk gevolg wordt gemakkelijk gemist: de oude URL blijft betekenisvol zolang een oud document of een oude runtime er nog naar kan verwijzen.
Dat de browser een bron met hash een jaar mag bewaren helpt niet wanneer juist die bron vóór de uitrol nooit is gedownload en de oorspronkelijke server haar inmiddels heeft verwijderd.
Daarom zijn ‘onze statische bestanden zijn onveranderlijk’ en ‘we kunnen de statische bestanden van de vorige release meteen verwijderen’ geen gelijkwaardige uitspraken. Onveranderlijkheid maakt het veilig om oude bronnen te bewaren; ze zorgt er niet voor dat oude clients er niet meer om vragen.
De huidige Next.js-handleiding voor zelfhosting noemt ontbrekende JavaScript- of CSS-bestanden expliciet als een symptoom van versieverschillen tijdens een uitrol met meerdere servers of een geleidelijke uitrol. Het is hetzelfde soort probleem wanneer het verschil ontstaat tussen een oud tabblad en een zojuist bijgewerkte oorsprongsserver in plaats van tussen twee tegelijk actieve servers.
Versies kunnen op verschillende manieren uiteenlopen
‘Cacheprobleem’ is te vaag om als diagnose bruikbaar te zijn. Ik onderscheid minstens vier mechanismen, omdat ze verschillende oplossingen vragen.
1. Een oud tabblad vraagt om een bron die vóór de uitrol nooit is geladen
Dit is het klassieke geval van een lang openstaand tabblad. Document en runtime kwamen uit release A. Release B vervangt de bestanden op de server. Later activeert de gebruiker een handeling waarvoor een uitgesteld geladen chunk uit A nodig is. Als die bron van A is verwijderd, mislukt de aanvraag.
2. Verouderde HTML verwijst naar chunks die niet meer bestaan
Een CDN, omgekeerde proxy, Service Worker, browsercache of statische hostinglaag kan een oudere HTML-versie langer bewaren dan verwacht. Die HTML kan nog verwijzingen van release A bevatten terwijl de oorsprongsserver alleen B heeft.
Dit is vooral gevaarlijk wanneer HTML per ongeluk een lang immutable-beleid krijgt. JavaScript met een hash en HTML moeten niet als hetzelfde cacheobject worden behandeld. De chunk kan onveranderlijk zijn omdat zijn URL door de inhoud wordt geversioneerd. De HTML bepaalt welke chunk-URL’s bij elkaar horen.
3. Een geleidelijke uitrol of meerdere instanties leveren gemengde releases
Stel je twee Next.js-instanties achter een taakverdeler voor. De ene draait al op release B, de andere nog op A. Een document kan van de ene versie komen terwijl een latere navigatieaanvraag op de andere terechtkomt. De huidige Next.js-documentatie noemt dit versieverschil en vermeldt dat het ontbrekende bronnen, niet-overeenkomende Server Functions en navigatiefouten kan veroorzaken.
De veiligste standaard is één keer compileren en hetzelfde compilatieartefact draaien op alle instanties die aan één uitrol deelnemen. De documentatie voor Next.js-zelfhosting raadt ook aan dezelfde compilatie en een consistente compilatie-ID in alle containers te gebruiken, in plaats van elke replica zelfstandig opnieuw te compileren.
4. De uitrol publiceert de bestanden in de verkeerde volgorde
Zelfs zonder oude tabbladen kan een niet-atomaire upload tijdelijk een onmogelijke toestand creëren:
nieuwe HTML is al zichtbaar
+
nieuwe chunkbestanden zijn nog niet beschikbaar
of andersom:
oude HTML is nog zichtbaar
+
oude chunkbestanden zijn al verwijderd
Een heel korte periode is genoeg. Een gebruiker hoeft er maar één keer in terecht te komen.
Het gevaarlijke uitrolpatroon is ‘alles vervangen en de oude boom verwijderen’
Een eenvoudig uitrolscript begint vaak ongeveer zo:
build
rsync --delete new-output/ production/
restart
Dat is aantrekkelijk omdat de productiemap altijd exact overeenkomt met de nieuwste compilatie. Voor lang openstaande clients is het juist onvriendelijk.
Bij statische bronnen met een hash levert het terugbrengen van de map tot precies één release de browser weinig op. Oude bestanden botsen niet met nieuwe, omdat hun URL’s verschillen. Verwijderen bespaart vooral schijfruimte, maar verandert iedere nog geldige verwijzing van een oude client in een mogelijke 404.
Ik beschouw oude chunks nu als compatibiliteitsmateriaal voor uitrollen, niet als afval.
Dat betekent niet dat elke compilatie eeuwig moet blijven bestaan. Opruimen hoort een afzonderlijk bewaarbeleid te zijn, geen toevallig neveneffect van het publiceren van de nieuwste release.
Bewaren helpt, maar geen eindige bewaartermijn is een volledige oplossing
Bij zelfhosting kun je oude /_next/static-bronnen gedurende een respijtperiode bewaren. De juiste duur hangt van het gebruik af. Een site waar iemand een pagina opent, twee minuten leest en vertrekt heeft een ander risicoprofiel dan een toepassing die de hele werkdag open blijft.
Een bruikbare manier om over de minimale bewaartermijn na te denken is:
bewaartermijn >=
verwachte levensduur van verouderde HTML
+ realistische levensduur van een lang openstaand tabblad
+ terugrolvenster
+ marge voor verspreiding van de uitrol
Dit is geen wiskundige garantie. Een browsertabblad kan weken open blijven. Geen enkel eindig aantal uren maakt fouten bij oude clients onmogelijk.
Daarom geef ik de voorkeur aan meerdere beschermingslagen:
- bewaar onveranderlijke bronnen van eerdere releases lang genoeg om normale oude sessies te laten doorgaan;
- detecteer versieverschillen zodat de client naar de huidige release kan gaan;
- bied één veilige herlaadpoging of een duidelijk zichtbaar herstelpad wanneer een bron werkelijk niet beschikbaar is;
- monitor ontbrekende eigen chunks zodat de bewaartermijn op werkelijke gegevens kan worden afgestemd.
De bewaarlaag voorkomt de meeste fouten. De herstellaag vangt de resterende gevallen op die geen eindige bewaartermijn volledig kan uitsluiten.
Ruim oude chunks niet op met een blind leeftijdscriterium
Ook een naïeve regel als ‘verwijder elk bestand dat ouder is dan zeven dagen’ kan fout zijn. Een huidige release kan een ouder bestand met hash hergebruiken waarvan de wijzigingsdatum oud is omdat de inhoud eenvoudigweg niet veranderd is.
Een sterker model voor het opruimen van ongebruikte bestanden houdt rekening met releases:
- bewaar manifesten of broninventarissen van iedere release die nog binnen het compatibiliteitsvenster valt;
- maak de vereniging van alle bronpaden waarnaar die releases verwijzen;
- verwijder nooit iets uit die beschermde verzameling;
- verwijder niet meer gebruikte bronnen pas na een extra respijtperiode.
Als dat voor een kleine uitrol te veel techniek is, is een bewust ruime map voor statische bronnen vaak goedkoper dan het onderzoeken van zeldzame clientfouten. Bestanden met een hash zijn hiervoor bijzonder geschikt: identieke inhoud hergebruikt vanzelf stabiele URL’s of kan in elk geval geen ongerelateerde inhoud overschrijven onder dezelfde gehashte naam.
De regel die ik zou vermijden is eenvoudig: maak --delete op de gedeelde /_next/static-boom niet onderdeel van dezelfde handeling waarmee de nieuwe release actief wordt gemaakt.
Next.js heeft expliciete bescherming tegen versieverschillen, maar bewaart daarmee geen oude bronnen
De huidige Next.js-versies ondersteunen deploymentId voor bescherming tegen versieverschillen. De configuratie kan er zo uitzien:
// next.config.js
const nextConfig = {
deploymentId: process.env.DEPLOYMENT_VERSION,
}
module.exports = nextConfig
Volgens de huidige Next.js-documentatie over deploymentId krijgen door het raamwerk beheerde statische bron-URL’s een parameter ?dpl=<deploymentId>, dragen navigatieaanvragen van de client informatie over de uitrol en meldt de server zijn uitrol-ID in antwoorden. Wanneer Next.js tijdens de navigatie een verschil detecteert, kan het een volledige navigatie uitvoeren in plaats van een clientnavigatie met onverenigbare gegevens voort te zetten.
?dpl=<deploymentId>
x-deployment-id
x-nextjs-deployment-id
data-dpl-id
Dat is waardevol, maar de functie kan niet meer dan waarvoor zij ontworpen is. De documentatie zegt uitdrukkelijk dat Next.js de binnenkomende parameter ?dpl= niet gebruikt om een aanvraag naar een bepaalde versie te sturen. De parameter dient om een oude cachekopie te vermijden. Als een zelfgehoste oorsprongsserver een oud bestand fysiek heeft verwijderd, maakt een queryparameter dat bestand niet opnieuw aan.
Ik gebruik deploymentId daarom als mechanisme om verschillen te detecteren en herstel in gang te zetten, niet als vervanging voor zorgvuldig uitrollen of het bewaren van oude bronnen.
Op platforms met versieafhankelijke routering kan de infrastructuur verder gaan. De huidige Vercel-documentatie over bescherming tegen versieverschillen beschrijft bijvoorbeeld versievasthouding, zodat door het raamwerk beheerde aanvragen terecht kunnen blijven komen bij de uitrol die de client oorspronkelijk heeft bediend. Dat is een platformfunctie en niet iets waarvan ik uitga bij een willekeurige Nginx- of CDN-opstelling.
De compilatie-ID en uitrol-ID lossen verwante maar verschillende problemen op
Next.js maakt tijdens next build ook een compilatie-ID. Als meerdere containers dezelfde uitrol moeten bedienen, mogen ze niet stilzwijgend verschillende compilaties worden doordat iedere server zelfstandig een compilatiestap uitvoert.
Een deterministische compilatie-ID kan worden gekoppeld aan een release-ID, bijvoorbeeld een Git-revisie:
// next.config.js
const nextConfig = {
generateBuildId: async () => process.env.GIT_SHA,
deploymentId: process.env.GIT_SHA,
}
module.exports = nextConfig
Dit voorbeeld is ter illustratie en komt niet uit mijn productiecode. De belangrijke architectuurregel is dat één logische release één samenhangend compilatieartefact en één uitrolidentiteit moet hebben op alle instanties die haar bedienen.
generateBuildId identificeert de Next.js-compilatie. deploymentId is specifiek gedocumenteerd voor bescherming tegen versieverschillen en voor het omzeilen van oude cachekopieën. Ze hangen samen, maar de namen als synoniemen behandelen maakt foutonderzoek moeilijker.
Ik zou bronnen publiceren voordat ik verkeer naar het nieuwe document omschakel
Een veiligere uitrolvolgorde is bewust asymmetrisch. Nieuwe onveranderlijke bronnen mogen bestaan voordat iemand ernaar verwijst. Nieuwe HTML mag daarentegen niet verwijzen naar bronnen die nog niet beschikbaar zijn.
In grote lijnen wil ik deze volgorde:
1. release B één keer compileren
2. de /_next/static-bronnen van B uploaden
3. controleren dat de vereiste bronnen werkelijk opvraagbaar zijn
4. de server en runtime van B starten of voorbereiden
5. de gezondheid van B controleren
6. nieuw documentverkeer atomair naar B omschakelen
7. de statische bronnen van A beschikbaar houden
8. B monitoren
9. oude bronnen later opruimen
Bij een statische uitvoer geldt hetzelfde principe: upload eerst de geversioneerde bronnen en publiceer daarna de HTML die ernaar verwijst. Bij serverrendering achter een omgekeerde proxy bereid je de nieuwe server voor en schakel je het verkeer pas om als die gezond is.
Terugrollen hoort symmetrisch te zijn. Door de map van de vorige release en de bijbehorende statische bronnen te bewaren kun je de toepassing terugzetten zonder oude bestanden achteraf opnieuw te moeten reconstrueren.
Dat maakt niet iedere terugrol veilig. Een databasemigratie of een onverenigbaar contract van de achterliggende dienst kan verhinderen dat een oude toepassingsversie werkt, ook als haar JavaScript nog aanwezig is. Het bewaren van statische bronnen lost de statische compatibiliteit op, niet elk compatibiliteitsprobleem tussen releases.
Een gedeelde map met onveranderlijke bronnen werkt goed voor eenvoudige zelfhosting
Voor een kleine Nginx-uitrol is een eenvoudig patroon om de huidige toepassingsrelease te scheiden van een gedeelde opslag voor statische bronnen.
Een voorbeeldindeling kan zijn:
/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/...
Elke uitrol voegt haar nieuwe /_next/static-bestanden toe aan de gedeelde map zonder bestanden van eerdere bewaarde releases te verwijderen. Nginx kan dat pad met een onveranderlijk cachebeleid aanbieden:
location ^~ /_next/static/ {
root /srv/app/shared;
add_header Cache-Control "public, max-age=31536000, immutable";
}
Deze configuratie is een voorbeeld en geen bewering over de exacte Nginx-configuratie die ik gebruikte. Een echte uitrol moet rekening houden met rechten, MIME-typen, compressievarianten, CDN-gedrag en de precieze indeling van de uitvoer.
Het belangrijke punt is architecturaal: de wijzigbare verwijzing naar de huidige release en de grotendeels alleen aangevulde opslag van geversioneerde bronnen hebben verschillende levenscycli.
HTML heeft een ander cachebeleid nodig dan chunks met een hash
De eenvoudigste manier om het probleem opnieuw te creëren is HTML cachen alsof het een bron met inhoudshash is.
Voor dynamisch gerenderde Next.js-pagina’s gebruikt het raamwerk normaal gesproken niet-cachebare antwoordsemantiek voor gebruikersspecifieke dynamische uitvoer. Statische en ISR-pagina’s volgen andere regels en een CDN kan die terecht cachen. Een statische uitvoer die door Nginx wordt aangeboden is nog afhankelijker van de kopteksten die de beheerder instelt.
Ik gebruik daarom niet één universele cacheregel voor ‘de website’. Ik denk in objectklassen:
bron met hash in /_next/static
lange max-age
immutable
veilig om te bewaren
HTML / routedocument
moet naar de nieuwe release kunnen overgaan
beleid hangt af van het rendermodel
mag niet langer leven dan de bronnen waarnaar het verwijst
RSC / navigatiegegevens / API-gegevens
eigen regels voor compatibiliteit en actualiteit
Als er een CDN tussen zit, kan het afhankelijk van het cacheontwerp nodig zijn om na een uitrol het pad van het nieuwe document te verversen. Oude chunks met een hash uit de CDN verwijderen alleen omdat er een nieuwe release is, werkt meestal averechts: als de oorsprong ze ook heeft verwijderd, verdwijnt de laatste kopie die een oude client nog had kunnen redden.
De Next.js-handleiding voor CDN-caching is hier nuttig omdat zij paginacaching onderscheidt van het jaarlijkse immutable-beleid voor /_next/static-bronnen.
Automatisch herladen is een herstelmiddel, niet de primaire uitrolstrategie
Een veelgehoorde reactie op een mislukte chunk is ‘herlaad de pagina gewoon’. Dat werkt vaak doordat een volledige navigatie het huidige document ophaalt, dat naar de huidige compilatie verwijst.
Maar blind herladen bij iedere scriptfout veroorzaakt nieuwe problemen:
- een fout in een extern script kan een nutteloze herlaadactie veroorzaken;
- een echte serverstoring kan een oneindige herlaadlus opleveren;
- invoer in een niet-opgeslagen formulier kan verloren gaan;
- de status van React-componenten verdwijnt bij een volledige navigatie;
- dezelfde defecte uitrol kan na het herladen gewoon opnieuw falen.
De huidige Next.js-documentatie waarschuwt zelf dat de volledige navigatie die voor herstel bij versieverschillen wordt gebruikt componentstatus zoals useState kan verliezen, terwijl toestand in de URL of duurzame browseropslag kan blijven bestaan.
Als ik herstel aan de clientzijde toevoeg, wil ik dat het gericht is en slechts één keer gebeurt. Een voorbeeldimplementatie kan er zo uitzien:
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,
)
Dit is bewust alleen een voorbeeld. Een productie-implementatie moet ook rekening houden met chunks van stijlbladen, bekende foutvormen van het raamwerk, gebruikersstromen waarin herladen destructief is en de manier waarop de herstelmarkering na een gezonde laadactie wordt gewist.
Bij een editor, afrekenproces of lang formulier geef ik mogelijk de voorkeur aan een melding als ‘Er is een nieuwe versie beschikbaar; sla uw werk op en herlaad de pagina’ boven een gedwongen vernieuwing.
Monitoringgegevens moeten aantonen of dit werkelijk een versieverschil is
Een melding die alleen zegt ‘script kon niet worden geladen’ is niet genoeg. Om een verwijderde oude chunk te onderscheiden van een toevallige netwerkstoring heb ik context over de uitrol nodig.
Nuttige velden zijn:
- de URL van de bron die niet kon worden geladen;
- de URL van de huidige pagina;
- of de bron bij de eigen toepassing hoort;
- de release- of uitrol-ID die voor de client zichtbaar is;
- browser en besturingssysteem;
navigator.onLineals zwak signaal, niet als bewijs van bereikbaarheid;- tijd sinds het laden van de pagina;
- of de fout kort na een uitrol optrad;
- of dit de eerste herstelpoging was;
- HTTP-status wanneer die aan de serverzijde zichtbaar is;
- de release die de aanvraag op dat moment aan de oorsprong of proxy bediende.
Daarna worden de patronen veel informatiever.
Als veel gebruikers op verschillende netwerken oude gehashte chunk-URL’s opvragen en de oorsprong direct na een release 404 teruggeeft, is het ontbreken van bewaarde oude bronnen een sterke verklaring. Ziet slechts één gebruiker een netwerkfout zonder HTTP-antwoord, dan is een versieverschil veel minder zeker. Geeft de chunk 200 terug met het verkeerde MIME-type of met een HTML-foutpagina, dan zit het probleem in routering of proxyconfiguratie en niet alleen in het bewaarbeleid.
Ik zou fouten van eigen chunks ook apart alarmeren van storingen van externe bronnen. Dat is de monitoringwijziging die het rechtstreeksst uit mijn oorspronkelijke logboeken volgt: het betekenisvolle signaal zat vermengd met veel browserruis die niets met een defecte toepassing te maken had.
De reproductietest is eenvoudig, maar het oude tabblad moet open blijven
Dit soort fout wordt bij normale releasetests gemakkelijk gemist, omdat ontwikkelaars na een uitrol meestal meteen vernieuwen. Daarmee vernietigen ze precies de toestand die ze proberen te testen.
Een betere handmatige test is:
- release A uitrollen;
- een tabblad openen in productieachtige omstandigheden met browsercache ingeschakeld;
- slechts een deel van de toepassing bezoeken zodat sommige routes of uitgesteld geladen functies nog niet zijn geladen;
- dat tabblad open laten;
- release B uitrollen;
- het oude tabblad niet vernieuwen;
- een route of dynamische functie activeren waarvoor nog niet geladen code nodig is;
- Network en Console controleren;
- nagaan of oude bron-URL’s nog steeds 200 teruggeven;
- nagaan of de detectie van versieverschillen zo nodig een gecontroleerde volledige navigatie uitvoert.
Ik zou dezelfde test herhalen met een CDN ervoor, met twee serverinstanties tijdens een geleidelijke uitrol en nadat de ingestelde bewaartermijn is verlopen.
Een subtiele testfout is ‘Disable cache’ in DevTools voor alles inschakelen. Dat kan bij sommige diagnoses nuttig zijn, maar het verandert het browsergedrag. Het scenario van een lang openstaand tabblad moet ook met realistische caching worden getest, omdat de browsercache deel van het systeem uitmaakt.
Niet elke chunkfout wordt opgelost door oude bestanden te bewaren
Bewaren is juist krachtig omdat het één specifiek mechanisme oplost. Het mag niet weer een universele verklaring worden.
Een eigen chunk kan mislukken omdat:
- de aanvraag de server nooit bereikte;
- de verbinding werd onderbroken;
- een browseruitbreiding de aanvraag blokkeerde;
- een CDN-knooppunt een tijdelijke storing had;
- Nginx het pad verkeerd routeerde;
- de server een HTML-foutdocument in plaats van JavaScript terugstuurde;
- compressie of
Content-Encodingbeschadigd was; - de bestandsrechten verkeerd waren;
- een gedeeltelijke uitrol de chunk nooit heeft geüpload;
- het bestand wel bestond maar te vroeg werd verwijderd;
- client en server op onverenigbare releases draaiden.
De antwoordcode en het tijdstip zijn belangrijk. Een herhaalde 404 voor een oude URL met inhoudshash na iedere release vertelt een heel ander verhaal dan ERR_CONNECTION_RESET op één mobiel netwerk.
Daarom zou ik mijn oorspronkelijke incident niet herschrijven als ‘ik heb bewezen dat verouderde HTML de site brak’. Dat heb ik niet bewezen. Ik zag een echte fout van een eigen chunk en identificeerde versieverschillen als één ernstig mechanisme waarvoor het zinvol is de architectuur weerbaar te maken.
De veiligste uitrol behandelt oude clients als onderdeel van het releaseoppervlak
De diepere fout is denken dat een uitrol versie A op één enkel moment door versie B vervangt.
Op de server kan een symbolische koppeling of containerbeheerder dat beeld geven. Op het netwerk kunnen oude CDN-objecten nog aanwezig zijn. In browsers kunnen documenten van A lang blijven draaien nadat B actief is. Tijdens een geleidelijke release kunnen beide serverversies tegelijk actief zijn. Bij een terugrol kan B verdwijnen en A opnieuw de huidige versie worden.
Het werkelijke releaseoppervlak is dus een tijdsinterval, geen punt.
Mijn uitrolregels voor een Next.js-toepassing zijn nu rond dat idee opgebouwd:
- Compileer één keer per logische release. Laat replica’s niet stilzwijgend verschillende uitvoer produceren.
- Publiceer onveranderlijke bronnen voordat u de verwijzingen ernaar publiceert.
- Bewaar oude bronnen met hash gedurende een bewust gekozen compatibiliteitsvenster.
- Geef wijzigbare HTML niet hetzelfde cachebeleid als chunks met hash.
- Gebruik
deploymentIdwanneer het uitrolmodel versieverschillen kan veroorzaken. - Gebruik bescherming op platformniveau alleen wanneer het hostingplatform werkelijk versieafhankelijk kan routeren.
- Maak herstel eenmalig en houd rekening met gebruikersstatus.
- Monitor fouten van eigen chunks als afzonderlijk productiesignaal.
- Test een uitrol terwijl een oud tabblad nog openstaat.
- Ruim oude bronnen later op, niet tijdens het activeren van de nieuwe release.
De regel die ik nu gebruik
Een geslaagde compilatie en een gezonde nieuwe pagina bewijzen niet dat een uitrol veilig is voor gebruikers die al aanwezig waren.
Het oude tabblad is geen betekenisloos restant. Het is een echte client waarop een echte vorige release draait.
Toen ik op deze manier naar uitrollen begon te kijken, werd het chunkprobleem minder mysterieus. Een inhoudshash geeft een bron een stabiele identiteit. Langdurige caching maakt die identiteit efficiënt bruikbaar. Maar de uitrol moet die identiteit lang genoeg respecteren of de client een gecontroleerde manier geven om naar voren te gaan.
Ik hoef niet iedere oude release voor altijd in leven te houden. Ik moet ervoor zorgen dat het systeem de periode overleeft waarin oude clients en nieuwe servers terecht naast elkaar bestaan.
Dat is het uitrolcontract dat ik belangrijk vind: nieuwe gebruikers krijgen de nieuwe release, bestaande gebruikers verliezen niet de bestanden die hun huidige versie nog kan opvragen en elk resterend versieverschil leidt naar een bewust herstelpad in plaats van een kapotte pagina.