Tarayıcı cache'ini kendi lehinize çevirmek için content hash, query string versiyonlama ve Cache-Control: immutable direktifini nasıl birlikte kullanacağınızı, Webpack ve Vite'da otomatik fingerprinting kurulumunu açıklayan pratik rehber.

Static Asset Versiyonlama ve Cache Busting Stratejileri

Bir CSS dosyasını güncellediniz, sunucuya yüklediniz ve kullanıcının tarayıcısı hala eski versiyonu gösteriyor. Önbelleği temizlemesini söylemek, destek talebi olarak kabul etmek ya da "bir süre sonra düzelir" demek - bunların hiçbiri gerçek bir çözüm değil. Cache busting (önbellek kırma), tarayıcıların dosyaları agresif biçimde önbelleğe almasını kendi lehinize çevirmenizi sağlayan bir strateji grubudur.

Sorunun kökü, HTTP cache mekanizmasının nasıl tasarlandığında yatıyor. Tarayıcı bir dosyayı indirdiğinde, sunucu ona "bu dosyayı X süre boyunca geçerli say" der. Süre dolana kadar tarayıcı sunucuya sormaz bile. Bu davranış bant genişliğini azaltır ve sayfa yüklerini hızlandırır; ancak dosyayı değiştirdiğinizde tarayıcının yeni sürümü keşfetmesi için bir mekanizma olması gerekir. Cache busting tam da bu mekanizmayı sağlar.

Üç temel yaklaşım birbirini tamamlayarak çalışır: dosya adına içerik bazlı hash eklemek, daha eski projelerde tercih edilen query string versiyonlama ve HTTP başlıklarında immutable direktifi. Bu üçünü doğru birleştirdiğinizde hem agresif cache kullanabilir hem de kullanıcının her zaman güncel kodu almasını garanti edebilirsiniz.

Tarayıcı cache'i neden bu kadar inatçıdır

Tarayıcı, bir kaynağı indirdiğinde sunucunun gönderdiği Cache-Control başlığını okur. max-age=31536000 gören bir tarayıcı, o kaynağı bir yıl boyunca önbellekte tutar ve sunucuya tekrar sormaz. Bu çok agresif görünebilir; ancak statik assetler için tam olarak istenen davranıştır, çünkü içerik değişmediği sürece tekrar indirmek gerekmez.

Asıl problem, aynı URL'nin iki farklı içeriği temsil etmeye başladığı andır. /styles/main.css URL'si bugün bir şey, yarın başka bir şey anlamına geliyorsa tarayıcı bunu bilemez. Önbellekteki sürümün geçerlilik süresi dolmadan yeni içeriği otomatik çekme imkanı yoktur. Sert yenileme (Ctrl+Shift+R) bu sorunu tek bir kullanıcı için çözer; ama binlerce kullanıcı için bu yaklaşım ölçeklenemez.

Cache busting'in çözümü basit bir prensibe dayanır: içerik değiştiğinde URL de değişmeli. URL değişince tarayıcı gözünde bu yeni bir kaynak olduğundan önbellekte aramaz, doğrudan sunucudan indirir. Eski URL için tuttuğu önbellek ise doğal olarak kullanılmaz hale gelir. Sadelik burada.

Query string versiyonlama: ne zaman yeter, ne zaman yetmez

En sık görülen yaklaşım, dosya adını değiştirmeden URL'ye parametre eklemektir: main.css?v=1.2.3 veya main.css?v=20240528. Uygulaması kolaydır; template dosyasında tek bir değişkeni güncellemek yeterlidir. Mevcut bir server-side render edilen projede, build pipeline'ı kurmadan elde edilebilecek en hızlı çözümdür.

Ancak query string yöntemi bazı durumlarda beklediğiniz gibi çalışmaz. Çeşitli CDN'ler ve proxy sunucular, query string'i URL'nin bir parçası saymaz; önbellekleme kararını yalnızca yola göre verir. Bu durumda main.css?v=2 ile main.css?v=1 aynı önbellek kaydını paylaşır ve versiyon değişikliği CDN katmanına ulaşmaz. Sorunu tam olarak ortadan kaldırmak istiyorsanız, dosya adının kendisini değiştirmeniz gerekir.

Query string yöntemi, CDN kullanmayan veya proxy katmanı bulunmayan küçük projelerde yeterlidir. Versiyonu her deploy'da tutarlı biçimde güncellemek koşuluyla çalışır; elle takip etmek zorunda kalıyorsanız otomasyona geçmek için iyi bir zamandır.

Dosya adına hash gömmek: content hash nasıl çalışır

Content hash (içerik özeti), dosyanın baytlarından türetilen kısa bir karakter dizisidir. main.css yerine main.a3f8b2c1.css adı kullanılır. Dosya değişmediği sürece hash değişmez; bir karakter bile değişirse hash tamamen farklı bir değere dönüşür. Bu özellik deterministiktir: aynı içerik her zaman aynı hash'i üretir.

Yaklaşımın güçlü yanı, versiyonu elle yönetme zorunluluğunu ortadan kaldırmasıdır. Build aracı her dosya için hash'i otomatik hesaplar ve HTML referanslarını günceller. Dosya değişmediğinde URL değişmez, tarayıcı önbellekten serve eder. Dosya değiştiğinde URL değişir, tarayıcı yeniden indirir. Sistem tamamen otomatik çalışır ve insan hatası girme ihtimali sıfıra düşer.

Content hash, query string yönteminin aksine CDN'lerle tam uyumludur. Yol değiştiği için tüm önbellekleme katmanları bunu yeni bir kaynak olarak değerlendirir. Hangi dosyanın ne zaman değiştiğini takip etmek de kolaylaşır: hash'in değişip değişmediğine bakarak bundle değişimini doğrulayabilirsiniz.

Bir uyarı: çok sayıda küçük modülü tek bir büyük bundle'a dahil ettiğinizde, içeriden birini değiştirmek tüm bundle'ın hash'ini değiştirir ve kullanıcılar her güncellemede büyük bir dosyayı yeniden indirir. Kod bölme (code splitting) ve vendor chunk ayrımı bu sorunu azaltır. Vendor kütüphaneler ayrı bir chunk'ta olursa, uygulama kodunuzu değiştirdiğinizde vendor chunk'ın hash'i korunur ve kütüphaneler tekrar indirilmez.

Cache-Control: immutable ile uzun ömürlü önbellekleme

Cache-Control: max-age=31536000, immutable başlığı, önbellek süresi dolmadan tarayıcının yeniden doğrulama isteği göndermeyeceğini belirtir. Normal max-age kullanımında tarayıcılar zaman zaman koşullu GET isteği gönderebilir; immutable direktifi bunu engeller ve ağ trafiğini gereksiz yere harcamaz.

Firefox bu direktifi uzun süredir destekliyor. Chrome ve Safari kendi başlarına da revalidation'ı önbellek geçerliyken yapmayabilirler; yine de immutable eklemek zararlı değildir ve destekleyen tarayıcılarda belirgin bir fark yaratır. Direktif bilinmeyen tarayıcılar tarafından yok sayılır, geri uyumluluk sorunu çıkarmaz.

Bu başlığı yalnızca hash içeren URL'lerle kullanmak gerekir. /styles/main.css gibi sabit bir URL'de immutable kullanırsanız, dosyayı güncellediğinizde kullanıcılar önbellek dolana kadar eski sürümü almaya devam eder. Kuralı şöyle düşünebilirsiniz: URL'si içeriğe bağlı olduğu için asla değişmeyecek dosyalar için immutable, değişebilecek sabit URL'ler için kısa max-age ya da no-cache.

Hash içermeyen HTML dosyaları ve manifest dosyaları için kısa süreli veya sıfır önbellekleme uygulanmalıdır: Cache-Control: no-cache ya da max-age=0, must-revalidate. HTML dosyası değiştiğinde içindeki hash'li asset URL'leri de güncellenmiş olur; bu nedenle HTML'in her zaman taze gelmesi gerekir. Aksi halde tüm hash zinciri kopabilir.

Webpack'te content hash kurulumu

Webpack, output.filename ve output.chunkFilename alanlarında [contenthash] yer tutucusunu destekler. Temel yapılandırma şu şekilde görünür:

// webpack.config.js
module.exports = {
  output: {
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js',
    clean: true,
  },
};

:8 son eki hash'i 8 karaktere kısaltır; dosya adlarını makul uzunlukta tutar. clean: true her build öncesinde eski hash'li dosyaları temizler ve çıktı klasörünün şişmesini önler. CSS için MiniCssExtractPlugin ile aynı yaklaşımı uygularsınız:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

new MiniCssExtractPlugin({
  filename: '[name].[contenthash:8].css',
});

Vendor chunk'ını ayırmak için optimization.splitChunks kullanılır. Bu yapılandırma, node_modules'ten gelen kodları ayrı bir chunk'ta toplar ve uygulama kodunuz değiştiğinde bu chunk'ın hash'i korunur:

optimization: {
  splitChunks: {
    cacheGroups: {
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        chunks: 'all',
      },
    },
  },
  runtimeChunk: 'single',
},

runtimeChunk: 'single' önemlidir. Webpack'in modül kimliklerini yönettiği runtime kodu ayrı bir küçük dosyaya taşınır; bu olmadan her build'de modül kimlikleri değişebilir ve hash'ler gereksiz yere güncellenir. HTML referanslarını otomatik güncellemek için HtmlWebpackPlugin de eklenmelidir; plugin üretilen hash'li dosya adlarını HTML'e enjekte eder, template'i elle yönetme zorunluluğunu ortadan kaldırır.

Vite'da otomatik asset versiyonlama

Vite, production build'inde hash'li dosya adlarını varsayılan olarak üretir. Ek yapılandırma gerekmez; vite build çalıştırdığınızda çıktı klasöründe index-B3g7Xk2m.js ve index-A1c9Rp4n.css gibi dosyalar oluşur. Vite aynı zamanda index.html dosyasını da hash'li referanslarla otomatik günceller.

Hash uzunluğunu veya formatını özelleştirmek isterseniz build.rollupOptions kullanılır:

// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name]-[hash:8].js',
        chunkFileNames: 'assets/[name]-[hash:8].js',
        assetFileNames: 'assets/[name]-[hash:8][extname]',
      },
    },
  },
};

Vendor chunk ayrımı Vite'da da önerilir. manualChunks seçeneğiyle kütüphaneleri ayrı chunk'lara taşıyabilirsiniz:

rollupOptions: {
  output: {
    manualChunks: {
      react: ['react', 'react-dom'],
      router: ['react-router-dom'],
    },
  },
},

Vite, geliştirme sunucusunda hash kullanmaz; bu yalnızca production build çıktısıdır. Geliştirme ortamında hot module replacement (HMR) anlık güncelleme sağlar, hash gereksizdir. Statik görseller, fontlar ve diğer medya dosyaları için de aynı mantık geçerlidir: src/assets/ klasörüne koyduğunuz dosyalar Vite tarafından işlenir ve hash'li adlarla çıktıya kopyalanır. Doğrudan public/ klasörüne koyulan dosyalar ise işlenmeden kopyalanır ve hash almaz; bunlar için ayrı bir cache stratejisi gerekebilir.

HTML ve manifest dosyalarında cache stratejisi

Hash'li assetlerin işe yaraması, HTML'in her zaman güncel gelmesine bağlıdır. HTML dosyası eski hash'lere referans veriyorsa kullanıcı yine eski kodu alır. Bu nedenle HTML için immutable veya uzun max-age kullanmak hatalıdır. HTML, tüm zincirin giriş noktasıdır.

HTML için önerilen başlık şöyledir:

Cache-Control: no-cache, must-revalidate

Bu yapılandırma, tarayıcının her seferinde sunucuya sormasını sağlar. Sunucu ETag veya Last-Modified başlığıyla yanıt verirse ve dosya değişmemişse 304 Not Modified döner; tarayıcı yeniden indirmeden önbellekteki versiyonu kullanır. Dosya değiştiyse tam içerik gelir. HTML için hem önbelleği boşa harcamaz hem de içerik güncelliğini korursunuz.

Service worker dosyası (sw.js) ve web app manifestesi (manifest.json) için de benzer bir yaklaşım gerekir. Service worker güncellemelerinin tarayıcı tarafından doğru alınması, sw.js'nin önbelleğe alınmamasına ya da çok kısa süreyle önbelleğe alınmasına bağlıdır. PWA geliştiriyorsanız bu dosyaları hash'li URL'ye taşımak yerine kısa süreli önbellek veya no-cache tercih edin; aksi halde service worker güncellemesi beklenmedik biçimde gecikebilir.

Cache busting stratejisinin etkili olması için tüm parçaların birlikte çalışması gerekir: hash'li dosya adları, hash'li assetler için uzun ömürlü Cache-Control ve immutable, HTML için kısa ömürlü önbellek. Bunlardan birini eksik bıraktığınızda ya kullanıcılar gereksiz yere dosya indirmeye devam eder ya da güncellenen kodu göremez.

Webpack veya Vite dışında çalışan projelerde hash'i elle hesaplamak zahmetlidir; ancak imkansız değildir. Node.js'te bir dosyanın SHA256 özetini hesaplayıp dosya adına eklemek birkaç satır koddur ve mevcut bir projeye CI/CD adımı olarak eklenebilir. Hangi araçla uyguladığınızdan bağımsız olarak prensipler değişmez: URL sabitse önbellek uzun, URL içerik bazlıysa önbellek sonsuza kadar geçerli, HTML her zaman taze.

Stratejinin çalışıp çalışmadığını doğrulamak için tarayıcı geliştirici araçlarının ağ sekmesini açıp sayfayı yenileyin. Hash'li assetlerin yanında "from cache" veya 304 durumu görmelisiniz. HTML'nin ise her yenilemede sunucuya gittiğini ve no-cache başlığını taşıdığını kontrol edin. İki koşul birlikte sağlandığında sistem beklendiği gibi çalışıyor demektir.