Cloudflare ile Statik Dosyaları Önbellekleme: Browser Cache TTL Ayarları
Statik dosyaların önbelleklenmesi, web sitesi performansının temel taşlarından birini oluşturuyor. Cloudflare üzerinde bu işi doğru yapılandırdığınızda, hem sunucu yükünü dramatik biçimde düşürürsünüz hem de son kullanıcıların sayfaları çok daha hızlı yüklemesini sağlarsınız. Ama “Browser Cache TTL” ayarını yanlış yapılandırmak, tam tersi bir tablo ortaya çıkarabilir: eski içeriklerin kullanıcılarda takılı kalması, cache’i temizleme telası, deployment sonrası kriz anları. Bu yazıda Cloudflare’in Browser Cache TTL mekanizmasını derinlemesine inceleyeceğiz ve gerçek dünya senaryolarıyla nasıl optimize edeceğinizi göstereceğim.
Cloudflare Cache Mimarisi: Önce Bunu Anlamak Lazım
Cloudflare’in cache sistemi iki katmanlı çalışır ve bu iki katmanı birbirine karıştırmak en yaygın yapılandırma hatalarından birini doğurur.
Edge Cache (CDN Cache): Cloudflare’in kendi veri merkezlerinde (PoP – Point of Presence) tutulan önbellek. Kullanıcı bir isteği gönderdiğinde, önce en yakın Cloudflare PoP’una ulaşır. Eğer içerik orada önbelleklenmiş durumdaysa, asıl sunucunuza hiç ulaşmadan yanıt döner. Bu katmanı kontrol eden başlıca başlık Cache-Control: s-maxage veya standart Cache-Control: max-age direktifidir.
Browser Cache: Kullanıcının kendi tarayıcısında tutulan önbellek. Tarayıcı, daha önce ziyaret ettiği kaynakları locale indirir ve TTL süresi dolana kadar sunucuya tekrar istek atmaz. Bu katmanı Cloudflare’deki “Browser Cache TTL” ayarı veya sunucunuzdan gelen Cache-Control: max-age başlığı kontrol eder.
Cloudflare’in Browser Cache TTL ayarı, aslında sunucunuzdan gelen cache direktiflerini override eder. Yani sunucunuz Cache-Control: max-age=3600 dese bile, Cloudflare panelinde Browser Cache TTL’yi 4 saat olarak ayarladıysanız, tarayıcıya giden yanıtta bu değer 4 saate çekilir.
Bu davranışı anlamak için küçük bir test yapalım:
# Cloudflare üzerinden geçen bir isteğin başlıklarını inceleyelim
curl -I https://example.com/assets/main.css
# Beklenen çıktı örneği:
# HTTP/2 200
# cache-control: max-age=14400
# cf-cache-status: HIT
# age: 3721
# expires: Thu, 15 Jun 2024 12:00:00 GMT
Buradaki cf-cache-status: HIT değeri, içeriğin Cloudflare edge’inden geldiğini gösterir. age değeri ise bu içeriğin edge cache’de kaç saniyedir tutulduğunu belirtir.
Browser Cache TTL Nedir ve Nasıl Çalışır?
Browser Cache TTL, Cloudflare’in tarayıcıya “bu dosyayı şu kadar süre yerelde tut, bana tekrar sorma” dediği süreyi belirler. Cloudflare panelinde Caching > Configuration > Browser Cache TTL altında bulunur.
Varsayılan değer Respect Existing Headers modunda çalışır, yani sunucunuzdan ne geliyorsa onu geçirir. Ama bunu manuel olarak ayarladığınızda şu seçenekler karşınıza çıkar:
- Respect Existing Headers: Orijin sunucunun Cache-Control başlığını aynen iletir
- 30 dakika: Kısa süreli önbellekleme, sık değişen içerikler için
- 1 saat: Dengeli seçenek
- 4 saat: Semistatic içerikler için iyi bir başlangıç
- 8 saat: Gün içinde değişmeyen içerikler
- 1 gün: CSS, JS bundle’ları için makul süre
- 1 ay, 1 yıl: Hash’li asset dosyaları için ideal
Gerçek dünyada bu değerin nasıl tepki verdiğini test etmek için şu scripti kullanabilirsiniz:
#!/bin/bash
# cache-test.sh - Cloudflare cache başlıklarını analiz eder
URL=$1
echo "=== Cache Analizi: $URL ==="
echo ""
HEADERS=$(curl -sI "$URL")
CF_STATUS=$(echo "$HEADERS" | grep -i "cf-cache-status" | awk '{print $2}' | tr -d 'r')
CACHE_CONTROL=$(echo "$HEADERS" | grep -i "^cache-control" | awk '{print $2}' | tr -d 'r')
AGE=$(echo "$HEADERS" | grep -i "^age:" | awk '{print $2}' | tr -d 'r')
EXPIRES=$(echo "$HEADERS" | grep -i "^expires:" | cut -d' ' -f2- | tr -d 'r')
echo "CF Cache Durumu: ${CF_STATUS:-Bulunamadı}"
echo "Cache-Control: ${CACHE_CONTROL:-Bulunamadı}"
echo "Age: ${AGE:-0} saniye"
echo "Expires: ${EXPIRES:-Belirtilmemiş}"
chmod +x cache-test.sh
./cache-test.sh https://example.com/assets/style.css
Statik Dosya Türlerine Göre Strateji Belirleme
Her dosya aynı TTL değerini hak etmez. Gelin dosya türlerine göre mantıklı bir strateji oluşturalım.
Hash’li Asset Dosyaları (En Uzun TTL)
Modern build araçları (Webpack, Vite, Parcel) dosya adlarına hash ekler. Örneğin main.a3f9b21c.js gibi. İçerik değiştiğinde hash değişir, dolayısıyla URL değişir. Bu tür dosyalar için 1 yıllık TTL son derece mantıklıdır çünkü URL değişmeden içerik değişmez.
# Nginx - Hash'li dosyalar için Cache-Control başlığı
location ~* .(js|css)$ {
# Dosya adında hash varsa (8 karakter hex)
if ($uri ~* "[a-f0-9]{8}.(js|css)$") {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Hash yoksa daha kısa tutun
add_header Cache-Control "public, max-age=86400";
}
Görseller ve Medya Dosyaları
Görseller genellikle URL’leri değişmeden güncellenmez (asset pipeline olmayan sitelerde), bu yüzden daha ihtiyatlı bir süre tercih etmek gerekir.
# Nginx - Medya dosyaları için yapılandırma
location ~* .(jpg|jpeg|png|gif|ico|svg|webp|avif)$ {
add_header Cache-Control "public, max-age=604800"; # 1 hafta
add_header Vary "Accept-Encoding";
}
location ~* .(mp4|webm|ogg|mp3|wav)$ {
add_header Cache-Control "public, max-age=2592000"; # 30 gün
}
Font Dosyaları
Fontlar nadiren değişir, uzun TTL uygundur:
location ~* .(woff|woff2|ttf|otf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin "*";
}
Cloudflare Page Rules ile Granüler Kontrol
Cloudflare’in genel Browser Cache TTL ayarı tüm siteye uygulanır. Ama belirli path’ler için farklı davranış istiyorsanız Page Rules (veya yeni nesil Cache Rules) kullanmanız gerekir.
Örnek senaryo: /api/ endpoint’lerini hiç önbellekleme, /assets/ altındakileri 1 yıl önbellekle.
# Cloudflare API üzerinden Cache Rule oluşturma
# Önce API token'ınızı ve Zone ID'nizi hazırlayın
CF_API_TOKEN="your_api_token_here"
ZONE_ID="your_zone_id_here"
# API endpoint'leri için cache bypass kuralı
curl -X POST
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/rulesets"
-H "Authorization: Bearer ${CF_API_TOKEN}"
-H "Content-Type: application/json"
-d '{
"name": "API No Cache Rule",
"kind": "zone",
"phase": "http_request_cache_settings",
"rules": [
{
"action": "set_cache_settings",
"action_parameters": {
"cache": false
},
"expression": "(http.request.uri.path matches "^/api/")",
"description": "API isteklerini onbellekleme"
}
]
}'
Statik asset’ler için uzun süreli cache kuralı:
# Assets dizini için uzun TTL kuralı
curl -X POST
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/page_rules"
-H "Authorization: Bearer ${CF_API_TOKEN}"
-H "Content-Type: application/json"
-d '{
"targets": [
{
"target": "url",
"constraint": {
"operator": "matches",
"value": "example.com/assets/*"
}
}
],
"actions": [
{
"id": "browser_cache_ttl",
"value": 31536000
},
{
"id": "cache_level",
"value": "cache_everything"
}
],
"status": "active",
"priority": 1
}'
Cache Purge Stratejisi: Deployment Sonrası Zorunlu Adım
Hash’siz dosyalar kullanıyorsanız (eski bir CMS, özel bir setup vb.), deployment sonrasında cache’i temizlemek hayat kurtarır. Cloudflare API üzerinden bunu otomatize etmek için şu script işinize yarar:
#!/bin/bash
# cloudflare-purge.sh
# Kullanim: ./cloudflare-purge.sh [url1] [url2] ... veya --all
CF_API_TOKEN="${CLOUDFLARE_API_TOKEN}"
ZONE_ID="${CLOUDFLARE_ZONE_ID}"
purge_all() {
echo "Tum cache temizleniyor..."
RESPONSE=$(curl -s -X POST
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache"
-H "Authorization: Bearer ${CF_API_TOKEN}"
-H "Content-Type: application/json"
-d '{"purge_everything": true}')
SUCCESS=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('success', False))")
if [ "$SUCCESS" = "True" ]; then
echo "Cache basariyla temizlendi!"
else
echo "Hata: $RESPONSE"
exit 1
fi
}
purge_urls() {
URLS_JSON=$(printf '%sn' "$@" | python3 -c "
import sys, json
urls = [line.strip() for line in sys.stdin]
print(json.dumps({'files': urls}))
")
echo "Secilen URL'ler temizleniyor: $@"
RESPONSE=$(curl -s -X POST
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache"
-H "Authorization: Bearer ${CF_API_TOKEN}"
-H "Content-Type: application/json"
-d "$URLS_JSON")
echo "$RESPONSE" | python3 -c "
import sys, json
d = json.load(sys.stdin)
if d.get('success'):
print('Secilen URL cache girisleri temizlendi!')
else:
print('Hata:', d.get('errors', []))
"
}
if [ "$1" = "--all" ]; then
purge_all
else
purge_urls "$@"
fi
CI/CD pipeline’ınıza entegre etmek için örnek GitLab CI snippet:
# .gitlab-ci.yml
deploy_production:
stage: deploy
script:
- echo "Deployment yapiliyor..."
- rsync -avz dist/ user@server:/var/www/html/
- echo "Cloudflare cache temizleniyor..."
- |
curl -s -X POST
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/purge_cache"
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}"
-H "Content-Type: application/json"
-d '{"files": [
"https://example.com/assets/main.css",
"https://example.com/assets/app.js"
]}'
only:
- main
environment:
name: production
Gerçek Dünya Senaryo: E-ticaret Sitesi Optimizasyonu
Bir müşterin e-ticaret sitesini optimize etmek zorunda kaldım. Sorunlar şunlardı: ürün görselleri her yerde yavaş yükleniyor, CSS/JS dosyaları her sayfada yeniden indiriliyor, sunucu yükü gereksiz yüksek. Mevcut durumda hiçbir cache header’ı yoktu.
Önce mevcut durumu analiz ettim:
#!/bin/bash
# Site genelinde cache durumunu analiz eden script
DOMAIN="example-ecommerce.com"
PAGES=("/" "/products" "/category/electronics" "/about")
for PAGE in "${PAGES[@]}"; do
echo "=== Sayfa: $PAGE ==="
RESULT=$(curl -sI "https://${DOMAIN}${PAGE}" | grep -E "(cache-control|cf-cache-status|age:|x-cache)")
if [ -z "$RESULT" ]; then
echo "UYARI: Hic cache header yok!"
else
echo "$RESULT"
fi
echo ""
done
Ardından Nginx’e cache header’larını ekledim:
# /etc/nginx/conf.d/cache-headers.conf
# HTML dosyalari - kisa tutun, dinamik icerik var
location ~* .html$ {
add_header Cache-Control "public, max-age=300, must-revalidate";
}
# CSS ve JS - build hash'i olmayan dosyalar icin 1 gun
location ~* .(css|js)$ {
add_header Cache-Control "public, max-age=86400, stale-while-revalidate=3600";
}
# Urun gorselleri - 1 hafta
location /uploads/products/ {
add_header Cache-Control "public, max-age=604800";
expires 7d;
}
# Statik asset klasoru - 1 ay
location /static/ {
add_header Cache-Control "public, max-age=2592000, immutable";
}
Cloudflare panelinde şu ayarları yaptım:
- Browser Cache TTL: Respect Existing Headers (sunucu header’larına güveniyoruz artık)
- Cache Level: Standard
- /uploads/ path’i için Cache Rule: Edge TTL 7 gün, Browser TTL 7 gün
- /static/ path’i için Cache Rule: Edge TTL 30 gün, Browser TTL 30 gün
Sonuç olarak sunucu istekleri %67 azaldı ve ortalama sayfa yüklenme süresi 3.2 saniyeden 1.1 saniyeye düştü.
Stale-While-Revalidate: Modern Cache Stratejisi
stale-while-revalidate direktifi, cache süresi dolduğunda tarayıcının eski (stale) içeriği göstermeye devam etmesine izin verirken arka planda yeni versiyonu getirmesini sağlar. Cloudflare bu direktifi destekler ve edge cache’de de benzer davranış uygular.
# Stale-while-revalidate testi
curl -I https://example.com/assets/styles.css
# Ideal cevap:
# cache-control: public, max-age=86400, stale-while-revalidate=3600
# Bu: 1 gun taze, suresinin dolmasindan 1 saat sonrasina kadar "bayat ama kullanilabilir"
Nginx yapılandırmasında:
location /assets/ {
add_header Cache-Control "public, max-age=86400, stale-while-revalidate=3600, stale-if-error=86400";
# stale-if-error: Origin sunucu hata verirse 1 gun daha eski icerigi sun
}
stale-if-error direktifi özellikle kritik öneme sahip. Sunucunuz çöktüğünde veya geçici hata verdiğinde, kullanıcılar eski ama çalışan bir sayfa görür, tamamen boş sayfa yerine.
Cloudflare Workers ile Dinamik Cache Kontrolü
Bazen daha granüler kontrol gerekir. Cloudflare Workers kullanarak request/response üzerinde tam kontrol sağlayabilirsiniz:
// worker.js - Akıllı cache yönetimi
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
// API isteklerini hic onbellekleme
if (url.pathname.startsWith('/api/')) {
const response = await fetch(request);
const newResponse = new Response(response.body, response);
newResponse.headers.set('Cache-Control', 'no-store, no-cache');
return newResponse;
}
// Hash'li asset dosyalari - 1 yil
const hashPattern = /.[a-f0-9]{8,}.(js|css|png|webp)$/;
if (hashPattern.test(url.pathname)) {
const cache = caches.default;
let response = await cache.match(request);
if (!response) {
response = await fetch(request);
const newResponse = new Response(response.body, response);
newResponse.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
event.waitUntil(cache.put(request, newResponse.clone()));
return newResponse;
}
return response;
}
// Diger statik dosyalar - 1 gun
if (url.pathname.match(/.(js|css|png|jpg|svg|woff2)$/)) {
const response = await fetch(request);
const newResponse = new Response(response.body, response);
newResponse.headers.set('Cache-Control', 'public, max-age=86400, stale-while-revalidate=3600');
return newResponse;
}
return fetch(request);
}
Sık Yapılan Hatalar ve Çözümleri
Hata 1: Cache-Control: no-cache ile no-store karıştırılması
no-cache, içeriği önbellekleme ancak her kullanımda sunucuyla doğrula demek. no-store ise içeriği hiçbir yerde saklamazsa demek. API token’ları ve kişisel veriler için no-store kullanın. Sık değişen içerikler için no-cache daha uygun.
Hata 2: Cloudflare Browser Cache TTL’yi çok uzun ayarlamak ve purge yapmayı unutmak
Eğer Browser Cache TTL’yi 1 yıl ayarladıysanız ve hash’siz dosyalar kullanıyorsanız, deployment sonrasında tarayıcılarda eski versiyon kalır. Bunu çözmek için ya hash’li URL’lere geçin ya da daha kısa TTL kullanın.
Hata 3: Vary header’ını göz ardı etmek
Sıkıştırılmış ve sıkıştırılmamış içerik aynı URL’den sunuluyorsa Vary: Accept-Encoding header’ı şarttır, aksi hâlde yanlış versiyon serve edilebilir.
Hata 4: Edge TTL ile Browser TTL’yi karıştırmak
Cloudflare panelinde Edge Cache TTL ve Browser Cache TTL ayrı ayrı ayarlanabilir. Edge TTL yüksek, Browser TTL düşük tutmak genellikle iyi bir stratejidir: Cloudflare hızlı sunar, tarayıcı sık kontrol eder.
Cache Warming: Deployment Sonrası Önbelleği Isıtmak
Yeni deployment sonrasında Cloudflare cache’i boşalır. Kullanıcılar sayfaları ilk ziyaret ettiğinde origin sunucunuza istek gider. Bunu önlemek için cache warming scripti çalıştırabilirsiniz:
#!/bin/bash
# cache-warmer.sh - Önemli sayfaların cache'ini önceden doldurun
DOMAIN="https://example.com"
URLS=(
"/"
"/products"
"/about"
"/assets/main.css"
"/assets/app.js"
"/images/hero.webp"
)
echo "Cache warming basliyor..."
for URL in "${URLS[@]}"; do
FULL_URL="${DOMAIN}${URL}"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$FULL_URL")
CF_STATUS=$(curl -sI "$FULL_URL" | grep -i "cf-cache-status" | awk '{print $2}' | tr -d 'r')
echo "[$STATUS] [$CF_STATUS] $FULL_URL"
sleep 0.5 # Rate limit'e takilmayin
done
echo "Cache warming tamamlandi!"
Sonuç
Cloudflare Browser Cache TTL yapılandırması, kulağa basit görünse de yanlış yapıldığında ciddi sorunlara yol açabilen kritik bir alan. Özetlemek gerekirse:
- Hash’li dosyalar için 1 yıl TTL ve
immutabledirektifi kullanın - Hash’siz ama nadir değişen dosyalar için 1 hafta ile 1 ay arasında TTL tercih edin
- Dinamik içerikler için Respect Existing Headers modunda kalıp sunucu tarafında kısa TTL ayarlayın
- API endpoint’leri için her zaman no-store kullanın
- Deployment sürecinize mutlaka otomatik cache purge adımı ekleyin
stale-while-revalidatedirektifini benimseyerek kullanıcı deneyimini iyileştirin- Cloudflare Workers ile özel cache mantığı gerektiren senaryoları ele alın
Bu yapılandırmaları hayata geçirdikten sonra, curl -I ve Cloudflare Analytics üzerinden cache hit oranınızı düzenli olarak takip edin. %80’in üzerinde hit oranı hedefleyin; bu oranın altında kalıyorsanız page rule’larınızı ve origin server header’larınızı yeniden gözden geçirmeniz gerekiyor demektir. İyi önbellekleme, sadece hız meselesi değil, sunucu maliyetleri ve ölçeklenebilirlik açısından da doğrudan etkisi olan bir konudur.
