# CDN'de asset sürümleme: hash'leme ve cache stratejisini nasıl kurarım?

> Query-string busting'i bırakın: asset'i content-hash'li adla `immutable` cache'leyin, HTML'i kısa ömürlü tutun, canlıya geçmeden önce yükleyin.

- Soruldu: 2026-05-24
- Yanıtlandı: 2026-05-27
- Soran: Aslı
- Etiketler: performans, ci-cd, altyapi
- Kaynak: https://muhammetsafak.com/tr/sor-bakalim/cdn-asset-surumleme-hashleme-ve-cache-stratejisi/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** DeployerPHP ile yeni kodu canlıya alınca kullanıcının tarayıcısındaki eski CSS/JS yüzünden arayüz kırılıyor. `mix.js?id=123` gibi query string'i bazı CDN'ler göz ardı ediyor, eski sürümü servis etmeye devam ediyor.

Kalıcı çözüm için asset adlarını hash'leyip (`app.a8f9b2.js`) `Cache-Control: max-age=31536000` ile CDN'e göndermek istiyorum. CI/CD'de derleme ve invalidate otomasyonunu nasıl tasarlarım?


Kısa cevap: Query-string ile cache busting güvenilmez; kalıcı çözüm content-hash'li dosya adı (`app.a8f9b2.js`) — ad sadece içerik değişince değişir, gerisini cache stratejisi halleder.

## Kısa cevap

Sorunun kökü şu: `app.js?id=123` deseninde dosya adı hep aynı. Bazı CDN'ler query string'i cache key'inden atar ya da yok sayar, dolayısıyla `id` değişse de cache'teki ESKİ dosyayı servis etmeye devam eder. Cache key'ini değiştirmeniz gerekiyor — query'yi değil, adı. Aynı "önbelleği kim tazeliyor" sorusunun API tarafını [edge cache ve invalidation kaydında](/tr/sor-bakalim/cloudflare-workers-kv-ile-edge-cache-ve-invalidation/) ele almıştım.

## Neden

1. **Query string bir cache key garantisi değildir.** Davranış CDN'e göre değişir; bazıları onu tamamen yok sayar. Adın kendisi ise her katmanda cache key'in parçasıdır.

2. **İçerik hash'i doğal bir sürüm numarasıdır.** Ad yalnız içerik değiştiğinde değişir; değişmediğinde aynı kalır, yani gereksiz yeniden indirme de olmaz.

3. **Deploy anı bir yarış koşuludur.** Yeni HTML henüz yüklenmemiş bir hash'e işaret ederse kullanıcı 404 alır; sıra yanlışsa hash'leme de kurtarmaz.

## Ne yapmalı

1. **Content-hash'li dosya adı kullanın.** `app.a8f9b2.js` gibi; hash dosyanın içeriğinden türer. Böylece her asset'i `Cache-Control: public, max-age=31536000, immutable` ile cache'leyebilirsiniz — `immutable` direktifi tarayıcıya "bunu bir daha doğrulama bile" der, gereksiz koşullu istekleri keser.

2. **HTML/giriş noktasını kısa ömürlü tutun.** Hash'li asset'lere işaret eden HTML'i `no-cache` ya da kısa TTL ile verin: HTML her zaman taze gelsin ki yeni hash'lere işaret etsin. Burayı uzun cache'lerseniz tüm zincir kilitlenir.

3. **CI/CD'de önce yükleyin, sonra canlıya geçin.** Vite manifest'i ile hash'li çıktı üretin ve asset'leri uygulamayı canlıya almadan **önce** CDN'e/bucket'a yükleyin. DeployerPHP akışında bunu "symlink'i çevirmeden önce upload" adımı olarak kurun.

4. **Önceki sürümün asset'lerini bir süre tutun.** Deploy anında sayfası açık olan kullanıcı hâlâ eski HTML'i çalıştırıyor ve eski hash'leri ister. DeployerPHP'nin `keep_releases` mantığı bu "in-flight" kullanıcıları korur.

**Sonuç:** Ben olsam content-hash'li, `immutable` cache'li asset + kısa ömürlü HTML + canlıya-geçmeden-önce-upload üçlüsünü kurardım. Gerçek content hashing'de pratikte asset purge'üne neredeyse hiç ihtiyacınız olmaz — ad değiştiği için eski dosya zaten dokunulmadan kalır, kimse onu istemez. Tek invalidate etmeniz gereken şey HTML'dir, o da zaten kısa cache'li. Query-string busting'i tamamen bırakın.

## İlgili Yazılar

- [CI/CD'de OIDC ve IAM Role ile şifresiz (secretless) AWS erişimini nasıl kurarım?](https://muhammetsafak.com/tr/sor-bakalim/ci-cd-de-secret-yonetimi-ve-oidc-ile-sifresiz-erisim/) — Sor Bakalım
- [Docker imaj boyutunu multi-stage build ve distroless ile nasıl küçültürüm?](https://muhammetsafak.com/tr/sor-bakalim/docker-imaj-boyutunu-multi-stage-ve-distroless-ile-kucultmek/) — Sor Bakalım
- [Terraform state'i S3 + DynamoDB locking ile nasıl güvenli yönetirim?](https://muhammetsafak.com/tr/sor-bakalim/terraform-state-yonetimi-s3-ve-dynamodb-ile-locking/) — Sor Bakalım
