โ† Semua pembelajaran / Astro Nol โ†’ Portal Berita
Fase 8 ยท Cloudflare: Cache, Edge & WAF

Cache key, Vary & kebocoran antar pembaca

Cache key menentukan respons mana yang dianggap sama. Kalau ia tidak memasukkan sesuatu yang membuat respons berbeda, pembaca akan melihat halaman milik orang lain.

Intisari

  • Cache key bawaan: host + jalur + query string. Cookie dan header tidak termasuk.
  • Artinya: respons yang berbeda karena cookie akan saling menimpa di cache kalau kamu memaksanya di-cache.
  • Vary dihormati sebagian; jangan mengandalkannya untuk memisahkan data personal.
  • Aturan yang benar: jangan cache yang personal, jangan mencoba memisahkannya lewat cache key.
  • Pengecualian yang sah: memisahkan berdasarkan perangkat atau bahasa, bukan berdasarkan identitas.

Cache key bawaan

https://portal.contoh.id/berita/rupiah-menguat?hal=2
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ””โ”€โ”€โ”€โ”€โ”€โ”˜
        host                    jalur           query

TIDAK termasuk: cookie, Authorization, User-Agent, Accept-Language

Skenario kegagalan

1. Member A membuka /akun. Respons berisi namanya, saldonya, riwayat tagihannya.
2. Cache Rule keliru menandainya "eligible for cache".
3. Cloudflare menyimpannya dengan key: portal.contoh.id/akun
4. Member B membuka /akun.
5. Cache HIT. Member B melihat data Member A.

Ini bukan bug yang menghasilkan error. Halamannya dimuat sempurna. Tidak ada apa pun di log aplikasimu, karena request-nya tidak pernah sampai ke origin. Yang terjadi berikutnya adalah laporan dari pembaca, dan pada titik itu data pribadi sudah tersaji ke jumlah orang yang tidak bisa kamu hitung.

Kenapa Vary: Cookie bukan jawabannya

// Terlihat masuk akal. Tidak menyelesaikan masalah.
res.headers.set("Vary", "Cookie");
MasalahPenjelasan
Dukungan CDN terbatasCloudflare hanya menghormati Vary pada header tertentu; Cookie tidak diperlakukan sebagai pemisah cache umum
Kardinalitas tak terbatasTiap kombinasi cookie jadi entri sendiri โ€” cache hit ratio jatuh ke nol
Cookie pihak ketiga ikutCookie analitik yang berbeda per pembaca membuat tiap orang punya entri sendiri

Aturan yang benar dan sederhana: respons personal tidak di-cache di edge sama sekali. Bukan di-cache dengan pemisahan yang cerdik โ€” tidak di-cache.

Pertahanan berlapis

// Lapis 1: kode halaman
setCache(Astro.response, "personal");   // โ†’ private, no-store

// Lapis 2: middleware, untuk halaman yang lupa
if (jalurPersonal(ctx.url.pathname)) {
  res.headers.set("Cache-Control", "private, no-store");
}
// Lapis 3: Cache Rule bypass untuk jalur personal (aturan 1 di materi sebelumnya)
// Lapis 4: Cache Rule bypass saat cookie sesi ada

Empat lapis untuk satu aturan terdengar berlebihan sampai kamu mempertimbangkan konsekuensinya. Halaman baru akan ditambahkan oleh orang yang tidak membaca materi ini; lapis 2 sampai 4 melindungi dari itu.

Pemisahan cache yang sah

Ada kasus di mana memisahkan cache memang benar โ€” yang membedakannya: pemisahannya berdasarkan kelompok, bukan berdasarkan identitas.

PemisahanKardinalitasSah?
Perangkat (ponsel / desktop)2โ€“3Ya
Bahasa2โ€“5Ya
NegaraPuluhanHati-hati
Tier langganan3Bisa, tapi server island lebih baik
ID memberRatusan ribuTidak pernah
User-Agent penuhPraktis tak terbatasTidak pernah
Cache Key โ†’ Custom:
  โ˜‘ Device Type          (mobile / desktop / tablet)
  โ˜ Cookie
  โ˜ Header
  โ˜‘ Query String: ignore utm_*, fbclid, gclid

Pemisahan per tier langganan terdengar menarik โ€” tiga versi halaman untuk gratis, dasar, dan pro. Tapi ia menggandakan jumlah entri cache, membuat purge tiga kali lebih rumit, dan tetap membutuhkan cookie di cache key untuk menentukan tier pembaca. Server island menyelesaikan hal yang sama dengan satu versi halaman ter-cache dan satu request kecil per pembaca. Pilih itu.

Bahaya yang berlawanan: Set-Cookie yang ter-cache

// BAHAYA: menyetel cookie di halaman yang boleh di-cache
Astro.cookies.set("pengunjung", id, { maxAge: 86400 });
setCache(Astro.response, "artikel");     // โ† publik!

Kalau Cloudflare meng-cache respons ini beserta header Set-Cookie-nya, maka setiap pembaca berikutnya menerima cookie yang sama. Untuk cookie pengunjung, itu berarti analitikmu menghitung sejuta orang sebagai satu. Untuk cookie sesi, itu berarti pengambilalihan akun massal.

Cloudflare secara bawaan tidak akan meng-cache respons yang punya Set-Cookie โ€” itu perlindungan yang bagus. Tapi Cache Rule yang memaksa "Eligible for cache" bisa menimpanya. Jadi: halaman yang di-cache tidak boleh menyetel cookie apa pun. Kalau butuh, setel dari server island yang memang tidak di-cache.

Audit

U=https://portal.contoh.id

# Tidak boleh ada Set-Cookie pada halaman publik
curl -sI $U/berita/artikel-uji | grep -i set-cookie   # harus kosong
curl -sI $U/                   | grep -i set-cookie   # harus kosong

# Jalur personal harus BYPASS, apa pun cookie-nya
for p in /akun /akun/tagihan /langganan; do
  printf '%s โ†’ ' "$p"
  curl -sI "$U$p" -H 'Cookie: __Host-sesi=uji' | grep -i cf-cache-status
done

# Uji kebocoran: dua cookie berbeda, bandingkan isinya
curl -s $U/akun -H 'Cookie: __Host-sesi=A' -o /tmp/a.html
curl -s $U/akun -H 'Cookie: __Host-sesi=B' -o /tmp/b.html
diff -q /tmp/a.html /tmp/b.html    # HARUS berbeda

Latihan: jalankan seluruh audit di atas pada portalmu. Lalu lakukan uji yang paling penting: buat dua sesi member berbeda, muat halaman akun dengan masing-masing beberapa kali, dan bandingkan hasilnya byte per byte. Tambahkan uji ini ke pipeline sebagai pemeriksaan asap setelah tiap deploy โ€” ini kelas bug yang harganya terlalu mahal untuk hanya diperiksa manual.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.