Cloudflare dashboard %1.1 cache rate gösteriyordu
Bu hafta düzenli olarak performance dashboard’a bakma alışkanlığı edindim. mustafaerbay.com.tr Cloudflare zone’unun istatistiklerini açtım. Total Requests 4.1k, Unique Visitors 1.16k. Güzel, oluyor. Sonra Percent Cached satırı:
Percent Cached: 1.11%
Yüzde bir virgül bir bir. Yani 4.1k request’in 4.05k tanesi her seferinde origin’e (VPS’ime) gidiyor. Cloudflare neredeyse hiçbir şey cache’lemiyor.
Bu sayı yanlış olsa neyse, gerçek. Bana bir kaç saniye lagsızlık ve VPS RAM’imi yiyor. Sebebini bulmam lazım.
İlk hipotez: Cloudflare yanlış configured
Belki cache rule ayarım yoktu. Dashboard’da Caching → Configuration → “Caching Level: Standard” diyordu. Standard demek “default’a göre cache et”. Default’ta HTML cache değil. Hmm. Ama nedeni “Caching Level” değil — daha temel bir sorun olmalı.
Origin’den ne header döndürdüğümü kontrol ettim:
$ curl -sI https://mustafaerbay.com.tr/blog/technology/some-post/ | grep -iE "cache|expires|content-type|cf-cache"
content-type: text/html; charset=utf-8
cache-control: public, max-age=0
last-modified: Tue, 28 Apr 2026 10:38:17 GMT
cf-cache-status: DYNAMIC
İşte. cache-control: public, max-age=0.
Cloudflare bu header’ı görünce “browser bile bunu cache etme dedi, ben de etmem” diyor → DYNAMIC. Edge cache 0%. Origin yapıyor.
Hashed asset’lere bakayım:
$ curl -sI https://mustafaerbay.com.tr/_astro/ClientRouter...js | grep cache
cache-control: public, max-age=31536000, immutable
cf-cache-status: HIT
JS’ler perfect — 1 yıllık immutable, Cloudflare HIT. Yani Astro hashed asset için immutable header’ı koyuyor. Sadece HTML’de max-age=0 koyuyor — büyük olasılıkla SSR rotaları için defensive default.
Astro Node adapter’ı suçlu
Astro’nun Node adapter’ı SSR rotaları için “her response taze” yaklaşımı kullanıyor. Ama benim sayfalarımın çoğu prerendered static HTML. Sadece /api/* ve birkaç dynamic sayfa SSR. Geri kalan static dosyalar.
Doğru davranış: HTML için cache header’ını manuel olarak override etmek. Astro Node adapter response’una direkt müdahale etmek karmaşık. Daha temiz: nginx layer’ında override.
nginx map directive ile content-type bazlı override
Nginx’te şu pattern çok güçlüdür:
map $upstream_http_content_type $mb_cache_control {
default $upstream_http_cache_control;
"~*^text/html" "public, max-age=300, s-maxage=3600, stale-while-revalidate=86400";
"~*^application/xml" "public, max-age=900, s-maxage=3600";
"~*^application/rss\+xml" "public, max-age=900, s-maxage=3600";
}
server {
# ... ssl, server_name vs ...
location / {
proxy_pass http://127.0.0.1:3040;
# ...
# Override Cache-Control for HTML/feeds; passthrough for assets and /api/*.
proxy_hide_header Cache-Control;
add_header Cache-Control $mb_cache_control always;
}
}
Mantık:
- Upstream (Astro Node) response’unu yakalar
Content-Typeheader’ına bakıyorum- Eğer
text/htmlise → s-maxage=3600 (CDN 1 saat cache) - Eğer
application/xmlise → 15 dk - Geri kalan (JS, CSS, image, JSON) → upstream’in kendi header’ını passthrough eder
proxy_hide_header upstream’in Cache-Control’unu siler. Sonra add_header benim map’lediğimi koyar. Trafik akışı:
Browser → Cloudflare (s-maxage'i okur, 1 saat cache) → nginx (override) → Astro
Bir tuzak: add_header always
Önce always flag’i olmadan kurmuştum. 4xx ve 5xx response’larda Cache-Control header gelmedi. /api/views için 404 dönerken cache-control: no-store gerekiyor (Astro upstream’i bunu koyuyor) ama nginx hide etmiş, kendi map’i 404 için davranmıyor → boş.
always ile çözülüyor:
add_header Cache-Control $mb_cache_control always;
Şimdi tüm status code’larda map devreye giriyor. application/json (API) → upstream value passthrough → no-store döner. text/html (404 sayfası) → max-age=300 → istemci 5 dakika cache. Bu da OK çünkü 404’lar fazla değişmiyor.
Doğrulama
$ curl -sI https://mustafaerbay.com.tr/blog/technology/some-post/ | grep cache
content-type: text/html; charset=utf-8
cache-control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400
cf-cache-status: HIT
Üç parametre:
- max-age=300 — browser 5 dk cache (yeni içeriği görmek için F5)
- s-maxage=3600 — Cloudflare 1 saat cache (CDN)
- stale-while-revalidate=86400 — Origin yavaşsa stale serve et + arka planda yenile
Bunu deploy ettikten sonra Cloudflare dashboard’a 24 saat verdim:
Percent Cached: 47.3%
%1.1 → %47.3. 47 katı. Origin’ime giden istek yarıdan az. VPS RAM’imin nefes alması, sayfa yüklenme süresinin yarıya inmesi, Türkiye dışı istekler için Cloudflare edge’inden hızlı response.
Daha geniş ders
Cache’i config etmek defaults’a güvenmemekle başlar. Astro Node adapter “her sayfa SSR olabilir” varsayımıyla max-age=0 koyuyor. Bu bir defensive default, anlaşılabilir. Ama benim sitem %95 prerendered, Astro’nun bilmediği bir şey. Override edip “ben biliyorum bu sayfa 1 saat değişmez” demek benim sorumluluğum.
İkinci ders — performance ölçümü vibe’la değil gözle metrikle yapılır. Percent Cached istatistiğini izlememek beni “site hızlı görünüyor” dünyasında tutardı. Dashboard’a bakmak, sayıyı görmek, “yüzde 1 ne için” diye sormak — bu refleks performance optimizasyonunun başlangıç noktası.
Yarın aynı dashboard’a tekrar bakacağım. 47’den yukarı gidecek mi yoksa o civarda asimptot çekecek mi göreceğim. Hangi bölümün cache’lenmediğine derin dalmak gerekirse de top 10 MISS path’lere bakacağım. Ama şu an: 47x improvement. İyi gün.