Bloga dön
13 Ağustos 2026Sergei Solod17 dk okuma

Eski Next.js sekmeleri dağıtımdan sonra neden bozulur: bayat HTML, eksik chunk dosyaları ve sürüm uyuşmazlığı

Bir dağıtımdan sonra üretim izleme sistemim, kendi Next.js uygulamama ait bir chunk dosyasının yüklenemediğini kaydetti. Kayıt hatanın gerçekleştiğini kanıtlıyordu, nedenini değil. Bu olaydan yola çıkarak uzun süre açık kalan sekmeleri, bayat HTML’i, eksik /_next/static kaynaklarını, sürüm uyuşmazlığını, eski dosyaları saklamayı, deploymentId kullanımını, yayın sırasını, izlemeyi ve kontrollü kurtarmayı ele alıyorum.

Next.jsDağıtımSürüm uyuşmazlığıWeb önbelleğiÖnyüz güvenilirliğiStatik kaynaklar

Bir dağıtımdan sonra üretimde gördüğüm en faydalı hatalardan biri ilk bakışta neredeyse sıradan görünüyordu:

Failed to load script:
/_next/static/chunks/9253.647385b4be0958e4.js

Aynı akışta analiz araçlarının hataları, reklam betikleri, genel Script error. iletileri ve yarıda kesilen video oynatmaları da vardı. Bu akışın büyük bölümü gürültüydü. Bu olay ise farklıydı: yüklenemeyen kaynak kendi Next.js uygulamama aitti. Tarayıcı gerçekten bu dosyayı alamadıysa sayfanın bir bölümü çalışmayı bırakabilirdi.

Ancak bu kayıttan chunk dosyasının neden yüklenemediğini bilmiyordum. Geçici bir ağ sorunu yaşanmış olabilir, ters vekil ya da CDN hata vermiş olabilir veya dosya gerçekten eksik olabilir. Bir başka olasılık da eski bir sayfanın, sunucu önceki yapıyı çoktan yenisiyle değiştirdikten sonra eski dağıtıma ait bir chunk dosyasını istemesiydi.

Son olasılığı küçümsemek kolaydır, çünkü yeni dağıtımın kendisi tamamen sağlıklı olabilir. Yeni ziyaretçiler güncel sürümü alır ve hiçbir sorun yaşamazken saatlerdir açık duran bir sekme sessizce önceki sürümün istemcisi olarak kalır.

Bu yazı tam olarak bu uyumluluk boşluğunu ele alıyor: eski Next.js sekmelerinin dağıtımdan sonra neden bozulabildiğini, bayat HTML ve eksik /_next/static kaynaklarının nasıl sürüm uyuşmazlığı oluşturduğunu, aşırı agresif temizliğin sorunu neden büyüttüğünü ve başarılı bir sürümün uygulamayı zaten açık tutan kullanıcıları bozuk bir sayfayla bırakmaması için dağıtımı, eski dosyaların saklanmasını, izlemeyi ve kurtarmayı nasıl tasarlayacağımı anlatıyor.

İlk ders: her betik hatasını dağıtım hatası saymamak gerekir

İlk hata akışında birbirinden tamamen farklı arıza türleri iç içeydi. Üçüncü taraf analiz ve reklam betikleri içerik engelleyiciler, DNS filtreleri, gizlilik özellikleri, bölgesel filtreleme, antivirüs yazılımları veya kullanıcının ağı tarafından engellenebilir. Bir videonun play() sözü, uygulama bozuk olmadığı hâlde daha sonra çağrılan pause() nedeniyle kesilebilir. Kaynaklar arası genel bir Script error. iletisi de çoğu zaman tanı koymak için yetersiz bilgi verir.

Kendi Next.js uygulamasına ait bir chunk dosyasının yüklenememesi farklı öncelik ister. Yararlı ayrım “JavaScript hatası var mı yok mu?” değildir; daha çok şuna benzer:

üçüncü taraf kaynağı yüklenemedi
    -> genellikle telemetri veya isteğe bağlı bir özellik etkilenir

uygulamanın kendi /_next/static/*.js dosyası yüklenemedi
    -> uygulama kodunun bir bölümü kullanılamayabilir

Bu ayrım önemlidir, çünkü fazla gürültülü bir hata raporlama sistemi gerçekten bozuk sayfalarla ilişkili hataları gizleyebilir. Benim olayımda önemli istek /_next/static/chunks/9253.647385b4be0958e4.js adresine gidiyordu. Kayıt, uygulamanın kendi betiğinin yüklenemediğini kanıtladı. Fakat sebebin dağıtımlar arasındaki sürüm uyuşmazlığı olduğunu kanıtlamadı.

Bu kanıt sınırını bilinçli olarak net tutuyorum: makul bir neden, doğrulanmış bir neden değildir.

Açık bir tarayıcı sekmesi fiilen eski bir sürümün istemcisidir

Soruna bakışımı değiştiren zihinsel model basit: bir dağıtımdan sonra uygulamanın birden fazla sürümü aynı anda yaşamaya devam edebilir.

A sürümünün saat 10.00’da yayında olduğunu düşünelim. Kullanıcı bir sayfa açar ve o rota için gereken HTML ile JavaScript’i alır. Saat 10.30’da B sürümü devreye girer. Yeni ziyaretçiler B’yi alır. Fakat daha önce açılmış sekme, yalnızca sunucu değişti diye kendiliğinden B sürümüne dönüşmez.

Bu sekmede hâlâ şunlar bulunabilir:

  • A sürümünden yüklenmiş JavaScript çalışma ortamı;
  • A sürümünün oluşturduğu rota ve chunk başvuruları;
  • A sürümünden önceden alınmış gezinme verileri;
  • A çalışırken oluşturulmuş React durumu;
  • A’dan daha önce indirilmiş bölünmüş kod modülleri;
  • A’ya ait olup henüz indirilmemiş modüllere başvurular.

Hata genellikle son maddede görünür hâle gelir.

Sayfanın ileride ihtiyaç duyacağı bütün chunk dosyaları zaten tarayıcı önbelleğindeyse kullanıcı hiçbir şey fark etmeden devam edebilir. Fakat modern Next.js uygulamaları kodu böler. Başka bir rotaya geçiş, dinamik içe aktarma, açılır pencere, düzenleyici ya da daha sonra kullanılan bir özellik yeni bir JavaScript dosyası gerektirebilir. Eski çalışma ortamı bu kez A sürümünde geçerli olan URL’den bir kaynak ister.

Sunucu o kaynağı hâlâ tutuyorsa her şey çalışmaya devam edebilir. Dağıtım dosyayı sildiyse, B sürümünün kendisi tamamen sağlıklı olsa bile eski istemci 404 alabilir.

İçerik özeti taşıyan chunk dosyaları uzun süre önbellekte tutulmak üzere tasarlanır

Next.js gerçekten değişmez kaynaklara bilinçli olarak uzun önbellek süresi verir. Güncel kendi sunucunda barındırma belgeleri, dosya adında SHA özeti bulunan değişmez kaynakların örneğin bir yıllık şu politikayla sunulduğunu belirtir:

Cache-Control: public, max-age=31536000, immutable

Bu mantıklıdır, çünkü içerik değiştiğinde URL de değişir. Adı içerikten türetilen bir dosyanın her istekte yeniden doğrulanması gerekmez. Sonraki derleme farklı baytlar üretiyorsa farklı bir kaynak URL’si de üretmelidir.

Önemli sonuç kolayca gözden kaçar: eski URL, eski bir belge veya eski bir çalışma ortamı ona başvurabildiği sürece anlamını korur.

Tarayıcının özetli bir kaynağı bir yıl saklayabilmesi, o belirli dosya dağıtımdan önce hiç indirilmediyse ve ilk ihtiyaç anında kaynak sunucu onu çoktan silmişse işe yaramaz.

Bu nedenle “statik dosyalarımız değişmez” ile “önceki sürümün statik dosyalarını hemen silebiliriz” aynı şey değildir. Değişmezlik, eski kaynakları saklamayı güvenli kılar; eski istemcilerin onları istemeyi bırakmasını sağlamaz.

Güncel Next.js kendi sunucunda barındırma kılavuzu, çok sunuculu veya kademeli dağıtımlarda eksik JavaScript ya da CSS dosyalarını sürüm uyuşmazlığının açık belirtilerinden biri olarak sayar. Uyuşmazlık aynı anda çalışan iki sunucu arasında değil de eski bir sekmeyle yeni güncellenmiş kaynak sunucu arasında olduğunda da sorun aynı sınıftadır.

Sürümler birkaç farklı şekilde birbirinden ayrılabilir

“Önbellek sorunu” tanı koymak için fazla belirsizdir. En az dört ayrı mekanizmayı birbirinden ayırıyorum, çünkü çözümleri farklıdır.

1. Eski sekme, dağıtımdan önce hiç yüklemediği bir kaynağı ister

Bu, uzun süre açık kalan sekmenin klasik örneğidir. Belge ve çalışma ortamı A sürümünden gelir. B sürümü sunucudaki dosyaları değiştirir. Kullanıcı daha sonra A’ya ait tembel yüklenen bir chunk gerektiren işlem yapar. A’nın kaynağı silinmişse istek başarısız olur.

2. Bayat HTML artık var olmayan chunk dosyalarına işaret eder

CDN, ters vekil, Service Worker, tarayıcı önbelleği veya statik barındırma katmanı eski bir HTML belgesini beklenenden uzun süre tutabilir. Bu HTML hâlâ A sürümüne ait başvurular içerirken kaynak sunucuda yalnızca B kalmış olabilir.

HTML yanlışlıkla uzun bir immutable politikası aldığında risk özellikle büyür. Özetli JavaScript ile HTML aynı önbellek nesnesi gibi ele alınmamalıdır. Chunk değişmez olabilir, çünkü URL’si içeriğe göre sürümlenir. Birlikte çalışması gereken chunk URL’lerini seçen şey HTML’dir.

3. Kademeli veya çok örnekli dağıtım farklı sürümleri karıştırır

Bir yük dengeleyicinin arkasında iki Next.js örneği düşünün. Biri B sürümüne geçmiş, diğeri hâlâ A’dadır. Belge bir sürümden gelirken sonraki gezinme isteği diğerine düşebilir. Güncel Next.js belgeleri buna sürüm uyuşmazlığı der ve eksik kaynaklara, Server Functions uyumsuzluklarına ve gezinme hatalarına yol açabileceğini belirtir.

En güvenli varsayılan yaklaşım, sürümü bir kez derlemek ve aynı dağıtıma katılan tüm örneklerde aynı derleme çıktısını çalıştırmaktır. Next.js kendi sunucunda barındırma belgeleri de her kopyayı bağımsız derlemek yerine tüm kapsayıcılarda aynı derlemeyi ve tutarlı bir derleme kimliğini kullanmayı önerir.

4. Dağıtımın kendisi dosyaları yanlış sırayla yayımlar

Eski sekmeler olmasa bile atomik olmayan yükleme kısa süreliğine imkânsız bir durum oluşturabilir:

yeni HTML görünür durumda
+
yeni chunk dosyaları henüz erişilebilir değil

ya da tersi:

eski HTML hâlâ görünür durumda
+
eski chunk dosyaları çoktan silinmiş

Çok kısa bir pencere yeterlidir. Kullanıcının o aralığa yalnızca bir kez denk gelmesi gerekir.

Tehlikeli dağıtım kalıbı “her şeyi değiştir ve eski ağacı sil” yaklaşımıdır

Basit bir dağıtım betiği çoğu zaman şöyle başlar:

build
rsync --delete new-output/ production/
restart

Üretim dizini her zaman en yeni derlemeyle bire bir aynı kaldığı için çekici görünür. Fakat uzun ömürlü istemciler için kötü bir yaklaşımdır.

Özetli statik kaynaklarda dizini yalnızca tek sürüme indirmenin tarayıcıya pek faydası yoktur. Eski dosyalar yeni dosyalarla çakışmaz; URL’leri farklıdır. Silmek esas olarak disk alanı kazandırır, fakat eski istemcide hâlâ geçerli olan her başvuruyu olası bir 404’e dönüştürür.

Artık eski chunk dosyalarını çöp değil, dağıtım uyumluluğunun bir parçası olarak görüyorum.

Bu, her derlemeyi sonsuza kadar saklamak gerektiği anlamına gelmez. Temizlik, yeni sürümü yayımlamanın yan etkisi değil, ayrı bir saklama politikası olmalıdır.

Eski kaynakları saklamak yararlıdır, ama sonlu hiçbir süre tek başına yeterli değildir

Kendi sunucunda barındırılan pratik bir kurulum, eski /_next/static kaynaklarını belirli bir geçiş süresi boyunca tutabilir. Doğru süre kullanım biçimine bağlıdır. Kullanıcının bir sayfayı açıp iki dakika okuyarak ayrıldığı site ile bütün iş günü açık kalan uygulamanın risk profili aynı değildir.

Asgari saklama süresini düşünmenin yararlı bir yolu şöyledir:

saklama süresi >=
    bayat HTML'in beklenen yaşam süresi
    + uzun süre açık sekmenin gerçekçi yaşam süresi
    + geri dönüş penceresi
    + dağıtımın yayılma payı

Bu matematiksel bir garanti değildir. Bir tarayıcı sekmesi haftalarca açık kalabilir. Sonlu hiçbir saat sayısı eski istemci hatalarını imkânsız hâle getirmez.

Bu yüzden katmanlı bir tasarımı tercih ediyorum:

  1. normal eski oturumların çalışmaya devam edebilmesi için önceki sürümlerin değişmez kaynaklarını yeterince uzun tutmak;
  2. sürüm uyuşmazlığını algılayıp istemcinin güncel sürüme geçmesini sağlamak;
  3. kaynak gerçekten yoksa güvenli, tek seferlik yeniden yükleme veya kullanıcının görebileceği bir kurtarma yolu sunmak;
  4. eksik kendi chunk dosyalarını izleyip saklama süresini gerçek verilere göre ayarlamak.

Saklama katmanı hataların çoğunu önler. Kurtarma katmanı ise sonlu hiçbir sürenin tamamen ortadan kaldıramayacağı kalan vakaları ele alır.

Eski chunk dosyalarını yalnızca yaşına bakarak temizlemeyin

“Yedi günden eski her dosyayı sil” gibi saf bir kural da yanlış olabilir. Güncel sürüm, içeriği değişmediği için değiştirilme zamanı eski olan bir özetli dosyayı yeniden kullanıyor olabilir.

Daha güçlü bir gereksiz dosya temizleme modeli sürümleri bilmelidir:

  1. uyumluluk penceresinde kalan her sürümün bildirim dosyalarını veya kaynak listelerini saklamak;
  2. bu sürümlerin başvurduğu tüm kaynak yollarının birleşimini oluşturmak;
  3. korunan bu kümedeki hiçbir şeyi silmemek;
  4. artık başvurulmayan kaynakları ancak ek bir bekleme süresinden sonra kaldırmak.

Bu mekanizma küçük bir dağıtım için fazla karmaşıksa, statik kaynak dizinini bilinçli olarak geniş tutmak çoğu zaman seyrek istemci hatalarını araştırmaktan daha ucuzdur. Özetli dosyalar buna özellikle uygundur; aynı içerik doğal biçimde kararlı URL’leri yeniden kullanır veya en azından aynı özetli ad altında ilgisiz içeriğin üzerine yazamaz.

Kaçınacağım kural basit: paylaşılan /_next/static ağacında --delete işlemini yeni sürümü etkinleştiren aynı işlemin parçası yapmamak.

Next.js sürüm uyuşmazlığına karşı açık koruma sunuyor, ancak bu eski kaynakları saklayan bir depo değil

Güncel Next.js, sürüm uyuşmazlığına karşı koruma için deploymentId desteği verir. Yapılandırma şöyle görünebilir:

// next.config.js
const nextConfig = {
  deploymentId: process.env.DEPLOYMENT_VERSION,
}

module.exports = nextConfig

Güncel Next.js deploymentId belgelerine göre bu ayar, çerçevenin yönettiği statik kaynak URL’lerine ?dpl=<deploymentId> parametresi ekler, istemci tarafındaki gezinme isteklerine dağıtım bilgisini koyar ve sunucunun yanıtlarda kendi dağıtım kimliğini bildirmesini sağlar. Next.js gezinme sırasında uyuşmazlık algılarsa, uyumsuz verilerle istemci tarafı geçişine devam etmek yerine tam sayfa gezinmesi yapabilir.

?dpl=<deploymentId>
x-deployment-id
x-nextjs-deployment-id
data-dpl-id

Bu değerlidir, ancak özelliğe sahip olmadığı güçleri yüklememek gerekir. Belgeler, Next.js’in gelen ?dpl= parametresini isteği belirli bir sürüme yönlendirmek için kullanmadığını açıkça söyler. Parametre eski önbellek kopyasını atlatmak içindir. Kendi barındırdığınız kaynak sunucu eski dosyayı fiziksel olarak sildiyse, sorgu parametresi dosyayı yeniden oluşturmaz.

Bu nedenle deploymentId değerini sürüm uyuşmazlığını algılama ve kurtarma mekanizması olarak görüyorum; düzenli dağıtımın veya eski kaynakları saklamanın yerine koymuyorum.

Sürüme duyarlı yönlendirme uygulayan platformlarda altyapı daha ileri gidebilir. Örneğin güncel Vercel sürüm uyuşmazlığı koruması belgeleri, çerçevenin yönettiği isteklerin istemciyi ilk sunan dağıtıma gitmeye devam etmesini sağlayan sürüm sabitlemesini açıklar. Bu bir platform yeteneğidir; rastgele bir Nginx veya CDN kurulumunda bulunduğunu varsaymam.

Derleme kimliği ile dağıtım kimliği ilişkili ama farklı sorunları çözer

Next.js, next build sırasında bir derleme kimliği de oluşturur. Birden fazla kapsayıcı aynı dağıtımı sunacaksa, her sunucu kendi bağımsız derleme adımını çalıştırdığı için sessizce farklı yapılara dönüşmemelidir.

Belirlenebilir bir derleme kimliği, Git revizyonu gibi sürüm kimliğine bağlanabilir:

// next.config.js
const nextConfig = {
  generateBuildId: async () => process.env.GIT_SHA,
  deploymentId: process.env.GIT_SHA,
}

module.exports = nextConfig

Bu örnek açıklama amaçlıdır ve üretim kodumdan kopyalanmamıştır. Önemli mimari kural şudur: tek mantıksal sürüm, onu sunan bütün örneklerde tek ve tutarlı bir derleme çıktısına ve tek bir dağıtım kimliğine sahip olmalıdır.

generateBuildId Next.js derlemesini tanımlar. deploymentId ise özellikle sürüm uyuşmazlığı koruması ve eski önbellek kopyalarını atlatmak için belgelenmiştir. Kavramlar ilişkilidir, ancak adlarını eş anlamlı görmek hata ayıklamayı zorlaştırır.

Trafiği yeni belgeye çevirmeden önce kaynakları yayımlardım

Daha güvenli bir dağıtım sırası bilinçli olarak asimetriktir. Yeni değişmez kaynaklar, kimse onlara başvurmadan önce var olabilir. Buna karşılık yeni HTML, henüz erişilemeyen kaynaklara işaret etmemelidir.

Kavramsal olarak şu sırayı isterim:

1. B sürümünü bir kez derle
2. B'nin /_next/static kaynaklarını yükle
3. gereken kaynakların gerçekten indirilebildiğini doğrula
4. B'nin sunucusunu ve çalışma ortamını başlat veya hazırla
5. B'nin sağlığını denetle
6. yeni belge trafiğini atomik olarak B'ye geçir
7. A'nın statik kaynaklarını erişilebilir tut
8. B'yi izle
9. eski kaynakları daha sonra temizle

Uygulama statik dışa aktarım ise aynı ilke geçerlidir: önce sürümlenmiş kaynakları yükleyin, sonra onlara başvuran HTML’i yayımlayın. Ters vekil arkasında sunucu taraflı oluşturma varsa, yeni sunucuyu hazırlayın ve yalnızca sağlıklı olduğu doğrulandıktan sonra trafiği çevirin.

Geri dönüş de simetrik olmalıdır. Önceki sürümün dizinini ve statik kaynaklarını tutmak, eski dosyaları sonradan yeniden oluşturmaya çalışmadan uygulamayı geri almayı mümkün kılar.

Bu, her geri dönüşü güvenli yapmaz. Veritabanı göçü veya arka uç hizmetindeki uyumsuz bir sözleşme, JavaScript dosyaları hâlâ dursa bile eski uygulama sürümünün çalışmasını engelleyebilir. Statik kaynakları saklamak statik uyumluluk sorununu çözer; sistemdeki her sürüm uyumluluğu sorununu değil.

Paylaşılan değişmez kaynak dizini basit kendi sunucunda barındırma için iyi çalışır

Küçük bir Nginx dağıtımında basit bir yaklaşım, uygulamanın güncel sürümünü paylaşılan statik kaynak deposundan ayırmaktır.

Örnek bir dizin yapısı şöyle olabilir:

/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/...

Her dağıtım yeni /_next/static dosyalarını paylaşılan dizine ekler ve saklanmaya devam eden önceki sürümlerin dosyalarını silmez. Nginx bu yolu değişmez önbellek politikasıyla sunabilir:

location ^~ /_next/static/ {
    root /srv/app/shared;
    add_header Cache-Control "public, max-age=31536000, immutable";
}

Bu yapılandırma yalnızca örnektir; kullandığım gerçek Nginx yapılandırmasının bire bir tarifi değildir. Gerçek dağıtımda izinler, MIME türleri, sıkıştırma çeşitleri, CDN davranışı ve çıktı yapısının tam biçimi hesaba katılmalıdır.

Önemli nokta mimaridir: güncel sürümü gösteren değişebilir işaretçi ile çoğunlukla üzerine ekleme yapılan sürümlenmiş kaynak deposunun yaşam döngüleri farklıdır.

HTML, özetli chunk dosyalarından farklı bir önbellek politikasına ihtiyaç duyar

Sorunu yeniden üretmenin en kolay yolu HTML’i içerik özetli bir kaynakmış gibi önbelleğe almaktır.

Dinamik olarak oluşturulan Next.js sayfalarında çerçeve, kullanıcıya özel dinamik çıktı için normalde önbelleğe alınmayan yanıt semantiği kullanır. Statik ve ISR sayfalarının kuralları farklıdır ve bir CDN bunları geçerli biçimde önbelleğe alabilir. Nginx tarafından sunulan statik dışa aktarım ise yöneticinin belirlediği başlıklara daha da bağımlıdır.

Bu yüzden “tüm site” için tek bir önbellek kuralı kullanmıyorum. Nesne sınıfları üzerinden düşünüyorum:

/_next/static içindeki özetli kaynak
    uzun max-age
    immutable
    saklamak güvenli

HTML / rota belgesi
    yeni sürüme geçebilmelidir
    politika oluşturma modeline bağlıdır
    başvurduğu kaynaklardan daha uzun yaşamamalıdır

RSC / gezinme verileri / API verileri
    ayrı uyumluluk ve güncellik kuralları

CDN kullanılıyorsa, önbellek tasarımına bağlı olarak dağıtımdan sonra yeni belgenin yolu temizlenmek zorunda olabilir. Yeni sürüm çıktı diye eski özetli chunk dosyalarını CDN’den temizlemek ise çoğu zaman ters etki yaratır: kaynak sunucu da bunları sildiyse, eski istemciyi kurtarabilecek son kopya da ortadan kalkar.

Next.js CDN önbellekleme kılavuzu burada yararlıdır, çünkü sayfa önbelleklemesini /_next/static kaynaklarındaki bir yıllık immutable politikasından ayırır.

Otomatik yeniden yükleme bir kurtarma aracıdır, ana dağıtım stratejisi değil

Chunk hatasına verilen yaygın yanıt “sayfayı yeniden yükle” olur. Bu çoğu zaman işe yarar, çünkü tam sayfa gezinmesi güncel derlemeye başvuran güncel belgeyi alır.

Ancak her betik hatasında körlemesine yeniden yüklemek yeni sorunlar oluşturur:

  • üçüncü taraf bir betik hatası gereksiz yeniden yüklemeye yol açabilir;
  • gerçek bir sunucu kesintisi sonsuz yeniden yükleme döngüsü oluşturabilir;
  • kaydedilmemiş bir formdaki kullanıcı verileri kaybolabilir;
  • tam sayfa gezinmesinde React bileşen durumu kaybolur;
  • aynı bozuk dağıtım tekrar hata verebilir.

Güncel Next.js belgeleri, sürüm uyuşmazlığından kurtarmak için kullanılan tam sayfa gezinmesinin useState gibi bileşen durumunu kaybettirebileceğini; URL’de veya kalıcı tarayıcı depolamasında bulunan durumun ise korunabileceğini açıkça belirtir.

İstemci tarafı kurtarma ekleyeceksem dar kapsamlı ve tek seferlik olmasını isterim. Örnek bir uygulama şöyle olabilir:

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,
)

Bu özellikle yalnızca bir örnektir. Üretim uygulamasında stil sayfası chunk dosyaları, çerçevenin bilinen hata biçimleri, yeniden yüklemenin kullanıcı çalışmasını bozacağı akışlar ve sağlıklı bir yüklemeden sonra kurtarma işaretinin nasıl temizleneceği de düşünülmelidir.

Düzenleyici, ödeme süreci veya uzun bir formda zorunlu yenileme yerine “Yeni bir sürüm var; çalışmanızı kaydedip sayfayı yeniden yükleyin” biçiminde bir uyarı göstermeyi tercih edebilirim.

İzleme verisi bunun gerçekten sürüm uyuşmazlığı olup olmadığını göstermelidir

Yalnızca “betik yüklenemedi” diyen bir ileti yeterli değildir. Silinmiş eski chunk dosyasını rastgele ağ hatasından ayırmak için dağıtıma ilişkin bağlam gerekir.

Yararlı alanlar şunlardır:

  • yüklenemeyen kaynağın URL’si;
  • geçerli sayfanın URL’si;
  • kaynağın uygulamanın kendisine ait olup olmadığı;
  • istemcinin görebildiği sürüm veya dağıtım kimliği;
  • tarayıcı ve işletim sistemi;
  • navigator.onLine değerinin bağlantı kanıtı değil, zayıf bir işaret olarak kullanılması;
  • sayfanın yüklenmesinden beri geçen süre;
  • hatanın dağıtımdan kısa süre sonra oluşup oluşmadığı;
  • bunun ilk kurtarma denemesi olup olmadığı;
  • sunucu tarafında görülebiliyorsa HTTP durumu;
  • isteği o anda kaynak sunucuda veya vekilde sunan sürüm.

Bundan sonra örüntüler çok daha anlamlı hâle gelir.

Farklı ağlardaki çok sayıda kullanıcı eski özetli chunk URL’lerini istiyor ve kaynak sunucu yeni sürümden hemen sonra 404 dönüyorsa, eski kaynakların saklanmamış olması güçlü bir açıklamadır. Yalnızca bir kullanıcı HTTP yanıtı olmadan ağ düzeyinde hata görüyorsa sürüm uyuşmazlığından çok daha az emin olabiliriz. Chunk 200 dönüyor ama yanlış MIME türü veya HTML hata sayfası içeriyorsa sorun yalnızca saklama değil, yönlendirme ya da vekil yapılandırmasıdır.

Ayrıca uygulamanın kendi chunk hataları için üçüncü taraf kaynak hatalarından ayrı uyarılar üretirdim. İlk kayıtlarımın en doğrudan desteklediği izleme değişikliği budur: anlamlı sinyal, uygulamanın bozulmasıyla ilgisiz büyük miktarda tarayıcı gürültüsünün içine karışmıştı.

Yeniden üretme testi basittir, ancak eski sekmeyi açık tutmak gerekir

Bu hata türü normal sürüm testlerinde kolayca kaçırılır, çünkü mühendisler dağıtımdan hemen sonra sayfayı yenileme eğilimindedir. Yenilemek, test etmeye çalıştığımız koşulu ortadan kaldırır.

Daha iyi bir elle test şöyle yapılır:

  1. A sürümünü dağıtın;
  2. tarayıcı önbelleği açık olacak şekilde üretime benzer koşullarda bir sekme açın;
  3. bazı rotalar veya tembel yüklenen özellikler henüz açılmamış kalsın diye uygulamanın yalnızca bir bölümünü kullanın;
  4. sekme açık kalsın;
  5. B sürümünü dağıtın;
  6. eski sekmeyi yenilemeyin;
  7. daha önce yüklenmemiş kod gerektiren bir rota veya dinamik özellik açın;
  8. Network ve Console panellerini inceleyin;
  9. eski kaynak URL’lerinin hâlâ 200 döndürdüğünü doğrulayın;
  10. sürüm uyuşmazlığı algılamasının gerektiğinde kontrollü tam sayfa gezinmesi yaptığını doğrulayın.

Aynı testi önde CDN varken, kademeli dağıtım sırasında iki sunucu örneğiyle ve yapılandırılmış saklama süresi dolduktan sonra da tekrarlardım.

İnce bir test hatası, DevTools içinde her şey için “Disable cache” seçeneğini açmaktır. Bazı tanılarda faydalı olabilir, ancak tarayıcı davranışını değiştirir. Uzun süre açık sekme senaryosu gerçekçi önbellekle de test edilmelidir, çünkü tarayıcı önbelleği sistemin bir parçasıdır.

Her chunk hatası eski dosyaları saklayarak çözülmez

Eski kaynakları saklamanın gücü, tam olarak tek bir dar mekanizmayı çözmesinden gelir. Başka bir evrensel açıklamaya dönüşmemelidir.

Uygulamanın kendi chunk dosyası şu nedenlerle yüklenemeyebilir:

  • istek sunucuya hiç ulaşmamıştır;
  • bağlantı kesilmiştir;
  • tarayıcı uzantısı isteği engellemiştir;
  • CDN düğümünde geçici hata yaşanmıştır;
  • Nginx yolu yanlış yönlendirmiştir;
  • sunucu JavaScript yerine HTML hata belgesi döndürmüştür;
  • sıkıştırma veya Content-Encoding bozulmuştur;
  • dosya izinleri yanlıştır;
  • kısmi dağıtım chunk dosyasını hiç yüklememiştir;
  • dosya vardı ama çok erken silinmiştir;
  • istemci ve sunucu uyumsuz sürümlerdedir.

Yanıt kodu ve zamanlama önemlidir. Her sürümden sonra eski içerik özetli URL için tekrarlanan 404, tek bir mobil ağdaki ERR_CONNECTION_RESET hatasından çok farklı bir hikâye anlatır.

Bu nedenle ilk olayımı “bayat HTML’in siteyi bozduğunu kanıtladım” diye yeniden yazmazdım. Bunu kanıtlamadım. Uygulamanın kendi chunk dosyasında gerçek bir yükleme hatası gördüm ve sürüm uyuşmazlığını tasarımla önlemeye değer ciddi mekanizmalardan biri olarak belirledim.

En güvenli dağıtım eski istemcileri sürüm yüzeyinin parçası sayar

Daha derin hata, dağıtımın A sürümünü tek bir anda B sürümüyle değiştirdiğini düşünmektir.

Sunucuda sembolik bağlantı veya kapsayıcı yöneticisi böyle gösteriyor olabilir. Ağda eski CDN nesneleri yaşamaya devam edebilir. Tarayıcılarda A belgeleri B yayına girdikten uzun süre sonra bile çalışabilir. Kademeli sürüm sırasında iki sunucu sürümü aynı anda etkin olabilir. Geri dönüşte B ortadan kalkıp A yeniden güncel hâle gelebilir.

Bu nedenle gerçek sürüm yüzeyi bir an değil, zaman aralığıdır.

Next.js uygulaması için dağıtım kurallarımı artık bu düşünce üzerine kuruyorum:

  • Her mantıksal sürümü bir kez derleyin. Kopyaların sessizce bağımsız çıktılar üretmesine izin vermeyin.
  • Değişmez kaynakları, onlara yapılan başvurulardan önce yayımlayın.
  • Eski özetli kaynakları bilinçli seçilmiş bir uyumluluk penceresi boyunca saklayın.
  • Değişebilir HTML’e özetli chunk dosyalarıyla aynı önbellek politikasını vermeyin.
  • Dağıtım modeli sürüm uyuşmazlığı oluşturabiliyorsa deploymentId kullanın.
  • Platform düzeyi korumayı yalnızca barındırma platformu gerçekten sürüme duyarlı yönlendirme sağlıyorsa kullanın.
  • Kurtarmayı tek seferlik yapın ve kullanıcının durumunu dikkate alın.
  • Uygulamanın kendi chunk hatalarını ayrı bir üretim sinyali olarak izleyin.
  • Dağıtımı eski bir sekme hâlâ açıkken test edin.
  • Eski kaynakları yeni sürümü etkinleştirirken değil, daha sonra temizleyin.

Şimdi kullandığım kural

Başarılı bir derleme ve sorunsuz çalışan yeni açılmış bir sayfa, dağıtımın uygulamayı zaten kullanan kişiler için güvenli olduğunu kanıtlamaz.

Eski sekme önemsiz bir artık değildir. Gerçek bir önceki sürümü çalıştıran gerçek bir istemcidir.

Dağıtımlara bu şekilde bakmaya başladıktan sonra chunk sorunu daha az gizemli hâle geldi. İçerik özeti bir kaynağa kararlı kimlik verir. Uzun süreli önbellekleme bu kimliği verimli kılar. Ancak dağıtım sistemi bu kimliğe yeterince uzun süre saygı göstermeli veya istemciye kontrollü biçimde yeni sürüme geçme yolu sunmalıdır.

Her eski sürümü sonsuza kadar yaşatmam gerekmiyor. Sistemin, eski istemciler ile yeni sunucuların meşru olarak birlikte bulunduğu dönemi atlatması gerekiyor.

Benim için önemli dağıtım sözleşmesi şu: yeni kullanıcılar yeni sürümü alır, eski kullanıcılar kendi sürümlerinin hâlâ isteyebildiği dosyaları kaybetmez ve kalan her sürüm uyuşmazlığı bozuk bir sayfa yerine tasarlanmış bir kurtarma yoluna çıkar.