← Semua pembelajaran / Laravel Nol → Enterprise
Fase 7 · Performa & Skala

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-Control adalah header yang menentukan segalanya. max-age untuk browser, s-maxage untuk CDN.
  • stale-while-revalidate menyajikan versi lama seketika sambil menyegarkan di latar — pengguna tidak pernah menunggu.
  • ETag dan Last-Modified menghasilkan 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

LapisanMenghematDikendalikan oleh
Browser penggunaSeluruh perjalanan jaringanmax-age
CDNSeluruh pemrosesan di servermus-maxage
Cache aplikasi (Redis)Query dan renderKodemu

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');
DirektifArtinya
publicBoleh disimpan cache bersama (CDN)
privateHanya browser pengguna itu. Untuk halaman personal
no-storeJangan simpan di mana pun. Untuk halaman berisi data sensitif
max-age=60Browser menganggapnya segar selama 60 detik
s-maxage=300CDN menganggapnya segar selama 300 detik — menimpa max-age
stale-while-revalidate=600Setelah kedaluwarsa, sajikan yang lama sambil ambil yang baru
must-revalidateSetelah kedaluwarsa, wajib tanya server dulu
immutableTidak 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

JenisCache-ControlAlasan
Aset ber-hash (app-4f2a.js)public, max-age=31536000, immutableNama berubah saat isi berubah
Gambar unggahanpublic, max-age=604800Jarang berubah setelah diunggah
Halaman artikelpublic, s-maxage=300, stale-while-revalidate=600Sama untuk semua pembaca
Halaman depan / kanalpublic, s-maxage=60, stale-while-revalidate=300Sering berubah, tapi tetap sama untuk semua
Umpan RSS, sitemappublic, s-maxage=600Diambil mesin, bukan manusia
Hasil pencarianpublic, s-maxage=30Melindungi dari pengerukan berulang
Dasbor, profil, keranjangprivate, no-storeBerbeda per pengguna
Endpoint POST/PUT/DELETEno-storeTidak pernah boleh di-cache

Invalidasi saat konten berubah

StrategiKelebihanKekurangan
TTL pendek + stale-while-revalidateSederhana, tanpa integrasi apa punPembaruan terlihat setelah beberapa menit
Invalidasi CDN saat menyimpanPembaruan langsung terlihatPerlu memanggil API CDN; ada kuota dan biaya
URL yang mengandung versiTidak perlu invalidasi sama sekaliMengubah 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.