HTTP caching & CDN
Cache aplikasi mempercepat penanganan permintaan. HTTP caching menghapus permintaannya. Untuk halaman yang sama bagi semua orang, perbedaan keduanya bukan persen — melainkan orde besaran.
Intisari
Cache-Controladalah header yang menentukan segalanya.max-ageuntuk browser,s-maxageuntuk CDN.stale-while-revalidatemenyajikan versi lama seketika sambil menyegarkan di latar — pengguna tidak pernah menunggu.ETagdanLast-Modifiedmenghasilkan 304 Not Modified: respons tanpa isi.- Halaman yang berbeda per pengguna tidak boleh di-cache bersama. Satu kesalahan di sini membocorkan sesi orang lain.
- Invalidasi CDN saat konten berubah — atau pakai TTL pendek dengan
stale-while-revalidate.
Tiga tempat sebuah halaman bisa di-cache
| Lapisan | Menghemat | Dikendalikan oleh |
|---|---|---|
| Browser pengguna | Seluruh perjalanan jaringan | max-age |
| CDN | Seluruh pemrosesan di servermu | s-maxage |
| Cache aplikasi (Redis) | Query dan render | Kodemu |
Urutan itu bukan kebetulan. Sebuah permintaan yang dijawab CDN tidak menyentuh PHP, tidak menyentuh database, dan tidak dihitung sebagai beban apa pun bagi aplikasimu. Untuk situs yang didominasi pembaca anonim, memindahkan halaman ke lapisan ini adalah pengungkit tunggal terbesar yang kamu punya.
Header yang menentukan
// Halaman artikel publik — sama untuk semua orang
return response()->view('artikel.show', compact('artikel'))
->header('Cache-Control', 'public, max-age=60, s-maxage=300, stale-while-revalidate=600');
| Direktif | Artinya |
|---|---|
public | Boleh disimpan cache bersama (CDN) |
private | Hanya browser pengguna itu. Untuk halaman personal |
no-store | Jangan simpan di mana pun. Untuk halaman berisi data sensitif |
max-age=60 | Browser menganggapnya segar selama 60 detik |
s-maxage=300 | CDN menganggapnya segar selama 300 detik — menimpa max-age |
stale-while-revalidate=600 | Setelah kedaluwarsa, sajikan yang lama sambil ambil yang baru |
must-revalidate | Setelah kedaluwarsa, wajib tanya server dulu |
immutable | Tidak akan pernah berubah. Untuk aset ber-hash |
stale-while-revalidate adalah direktif yang paling kurang dimanfaatkan. Tanpanya,
permintaan pertama setelah cache kedaluwarsa harus menunggu servermu — dan pada halaman populer, banyak
permintaan menunggu bersamaan. Dengannya, semua orang tetap dilayani seketika dari salinan lama sementara satu
permintaan menyegarkannya di latar. Ini cache stampede yang diselesaikan di lapisan CDN.
304 Not Modified
public function show(Artikel $artikel, Request $request)
{
$response = response()->view('artikel.show', compact('artikel'));
$response->setLastModified($artikel->updated_at);
$response->setEtag(md5($artikel->id.$artikel->updated_at->timestamp));
$response->setPublic();
// Kalau klien sudah punya versi ini, kirim 304 tanpa isi sama sekali
if ($response->isNotModified($request)) {
return $response;
}
return $response;
}
Respons 304 tidak membawa isi halaman — hanya header. Untuk halaman berukuran 80 KB, itu penghematan bandwidth yang hampir sempurna bagi pengunjung yang kembali. Tetap saja, permintaannya sampai ke servermu; menghemat pemrosesan tetap memerlukan cache di CDN.
Yang berbahaya: mencampur halaman personal
// BAHAYA — halaman ini berisi nama pengguna, tapi ditandai boleh di-cache bersama
return response()->view('dasbor')
->header('Cache-Control', 'public, s-maxage=300');
Ini kebocoran data paling parah yang bisa dihasilkan salah konfigurasi cache. CDN menyimpan satu
salinan halaman dasbor milik pengguna A, lalu menyajikannya kepada pengguna B, C, dan D. Aturan yang harus
dipegang tanpa kecuali: halaman yang isinya bergantung pada siapa yang login harus
private atau no-store. Pastikan juga cookie sesi tidak pernah ikut jadi
bagian dari kunci cache halaman publik.
// Middleware yang memutuskan berdasarkan status login
class AturCacheHalaman
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
if ($request->user() || $request->isMethod('POST')) {
return $response->header('Cache-Control', 'private, no-store');
}
if ($response->isSuccessful() && $request->isMethod('GET')) {
$response->header('Cache-Control',
'public, max-age=60, s-maxage=300, stale-while-revalidate=600');
}
return $response;
}
}
Kebijakan per jenis halaman
| Jenis | Cache-Control | Alasan |
|---|---|---|
Aset ber-hash (app-4f2a.js) | public, max-age=31536000, immutable | Nama berubah saat isi berubah |
| Gambar unggahan | public, max-age=604800 | Jarang berubah setelah diunggah |
| Halaman artikel | public, s-maxage=300, stale-while-revalidate=600 | Sama untuk semua pembaca |
| Halaman depan / kanal | public, s-maxage=60, stale-while-revalidate=300 | Sering berubah, tapi tetap sama untuk semua |
| Umpan RSS, sitemap | public, s-maxage=600 | Diambil mesin, bukan manusia |
| Hasil pencarian | public, s-maxage=30 | Melindungi dari pengerukan berulang |
| Dasbor, profil, keranjang | private, no-store | Berbeda per pengguna |
| Endpoint POST/PUT/DELETE | no-store | Tidak pernah boleh di-cache |
Invalidasi saat konten berubah
| Strategi | Kelebihan | Kekurangan |
|---|---|---|
TTL pendek + stale-while-revalidate | Sederhana, tanpa integrasi apa pun | Pembaruan terlihat setelah beberapa menit |
| Invalidasi CDN saat menyimpan | Pembaruan langsung terlihat | Perlu memanggil API CDN; ada kuota dan biaya |
| URL yang mengandung versi | Tidak perlu invalidasi sama sekali | Mengubah URL — tidak cocok untuk halaman artikel |
// Observer yang meminta CDN membuang salinan lamanya
class ArtikelObserver
{
public function saved(Artikel $artikel): void
{
InvalidasiCdn::dispatch([
"/artikel/{$artikel->slug}",
"/kategori/{$artikel->kategori->slug}",
'/',
]);
}
}
Kombinasi yang paling praktis: TTL pendek di CDN (s-maxage 1–5 menit) dengan
stale-while-revalidate yang panjang, ditambah invalidasi eksplisit hanya untuk halaman
yang benar-benar penting terlihat segera — halaman artikel yang baru dikoreksi, misalnya. Kamu mendapat
kesegaran tanpa harus memetakan setiap kemungkinan URL yang terpengaruh.
Memeriksa hasilnya
curl -sI https://portal.test/artikel/kabar-pagi | grep -iE 'cache-control|x-cache|age|etag'
cache-control: public, max-age=60, s-maxage=300, stale-while-revalidate=600
x-cache: Hit from cloudfront
age: 42
etag: "a1b2c3"
x-cache: Hit berarti permintaan itu tidak pernah sampai ke aplikasimu. Rasio hit
CDN adalah metrik yang layak dipasang di dasbor — menaikkannya dari 60% ke 90% memangkas dua pertiga beban
servermu tanpa menyentuh satu baris kode aplikasi.
Latihan: pasang middleware AturCacheHalaman, lalu periksa dengan curl -I
bahwa halaman artikel publik mendapat public, s-maxage=... sementara halaman dasbor mendapat
private, no-store. Kesalahan pada pemeriksaan ini adalah kebocoran data — pastikan tesnya
ada di suite-mu.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.