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 immutable direktifi 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-revalidate direktifini 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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir