Response Cache
Lewati load() + render() + kompresi saat cache hit. Aman per-user lewat identity hash. Invalidasi dari server action dengan invalidate(key) / invalidateAll(prefix).
Sejak v0.6 Bosia menyimpan response cache di memori yang menyajikan HTML SSR, JSON yang diambil router klien saat navigasi (/__bosia/data/…), dan response GET +server.ts langsung dari byte terkompresi saat URL yang sama diminta lagi. Saat cache hit tidak ada load(), tidak ada render(), dan tidak ada kompresi — biasanya response di bawah satu milidetik.
Cache ini aman untuk user yang login karena key menyertakan hash dari cookie dan header yang namanya terdaftar di CACHE_KEYS. Dua user dengan session cookie berbeda mendapat entry cache berbeda.
Alur request
- Lookup
<normalized-path>?<sorted-query>|o=<origin>|i=<identity-hash>di cache. - Hit → kirim varian terkompresi yang cocok (brotli, gzip, atau identity) berdasarkan
Accept-Encoding. Selesai. - Miss → miss pertama pada sebuah kunci menjadi leader: menjalankan
metadata(),load(), render, membangun HTML, lalu mengirimnya terkompresi. Pembuatan salinan gzip + brotli untuk cache berjalan setelah response terkirim. - Miss bersamaan pada kunci yang sama menunggu leader, lalu cek ulang cache — hit dilayani dari cache, jadi N miss simultan membangun halaman sekali, bukan N kali. Jika leader melewatkan penulisan (mis. mengeset cookie atau response tidak cacheable), tiap waiter membangun sendiri-sendiri.
Hash identitas di sini sama dengan yang dipakai deduplikasi request — satu kontrak CACHE_KEYS mengisolasi pengguna di kedua mekanisme.
Isolasi per-user
Cache key berbentuk:
<normalized-path>?<sorted-query>|o=<origin>|i=<identity-hash>origin adalah url.origin request (protokol + host). Nilainya mengikuti header Host (dan X-Forwarded-* dengan TRUST_PROXY=true), jadi Host palsu tidak bisa meracuni salinan untuk pengunjung lain. Tiap alamat yang dilayani app (app.com, www.app.com, http:// dan https://) punya salinan sendiri; redirect alias ke satu alamat agar salinan dipakai bersama.
identity-hash dibangun dari setiap cookie DAN header yang namanya muncul di CACHE_KEYS. Default sudah menutup nama session umum:
CACHE_KEYS=session,sid,auth,token,jwt,AuthorizationTambahkan nama custom kalau app Anda pakai cookie atau header lain untuk autentikasi. Nilai non-kosong apa pun ikut hash; dua request dengan kumpulan nilai sama berbagi entry, dua dengan nilai berbeda tidak.
Kalau konten per-user sebuah rute tidak dikunci oleh apa pun di CACHE_KEYS, matikan cache rute itu (lihat di bawah).
Kelayakan
| Kondisi | Hasil |
|---|---|
export const cache = false pada rute |
Lewati read + write |
Method request ≠ GET |
Lewati read + write |
CSP_DIRECTIVES terset (CSP aktif) |
Lewati read + write |
CACHE_MAX_ENTRIES=0 |
Lewati read + write |
| Status response ≠ 200 | Lewati write |
Handler memanggil cookies.set() |
Lewati write |
?_invalidated=… di query |
Lewati read; tetap write |
Request data dengan _fresh=1 |
Lewati read; tetap write |
Request data dikirim sebagai POST |
Lewati read + write |
Loader atau handler mengatur Cache-Control: no-store, no-cache atau private |
Lewati write |
Vary selain Accept-Encoding (mis. Cookie, Origin, *) |
Lewati write |
Response +server.ts sudah punya Content-Encoding |
Lewati write |
Body melebihi CACHE_MAX_BODY_BYTES atau seperempat CACHE_MAX_BYTES |
Lewati write |
Skip tidak akan merusak response — sekadar jatuh ke jalur render normal.
Key cache adalah URL ditambah CACHE_KEYS, jadi tidak bisa menghormati Vary pada hal lain. Response yang mengatur Vary: Cookie (meski cookie itu ada di CACHE_KEYS) tidak di-cache; jangan pasang Vary dan andalkan CACHE_KEYS.
Opt-out per rute
Tambahkan export const cache = false; di +page.server.ts, <script module> milik +page.svelte, atau +server.ts. Salah satu file halaman yang bilang false sudah cukup untuk mematikan cache. Di layout (+layout.server.ts atau <script module> milik +layout.svelte), cache mati untuk semua halaman di bawahnya:
import type { CacheOption } from "./$types";
export const cache: CacheOption = false;Pakai ini untuk data live (ticker, counter per detik) atau halaman yang personalisasinya tidak ter-cover CACHE_KEYS.
Untuk memastikan berhasil, GET halamannya dua kali (bukan curl -I: HEAD tidak pernah mengisi cache) dan lihat header x-bosia-cache:
curl -s -o /dev/null http://localhost:3000/live; sleep 1
curl -s -o /dev/null -D - http://localhost:3000/live | grep -i x-bosia-cache
# tidak ada output → opt-out berhasil. x-bosia-cache: HIT → masih di-cache.Uji dengan cookie user yang sedang login (-b "session=…"): request anonim yang di-redirect atau dapat Set-Cookie tidak pernah di-cache, jadi terlihat opt-out padahal user yang login tetap dapat salinan cache. Pakai setelan production juga: dengan CSP aktif atau CACHE_MAX_ENTRIES=0 tidak akan pernah ada HIT.
cache dibaca sebelum load() jalan dan dipakai bersama oleh semua request, jadi atur saat module dimuat, jangan di dalam load(). setHeaders({ "cache-control": "no-store" }) hanya mencegah satu response disimpan; salinan cache tetap bisa disajikan. Apa pun yang beda per user harus masuk CACHE_KEYS atau cache = false.
`invalidate()` sisi server
Setelah write, hapus entry cache yang cocok supaya bacaan berikutnya menyajikan HTML segar:
import { invalidate } from "bosia/server";
export const actions = {
rename: async ({ request, locals }) => {
await db.users.update(locals.user.id, { name: (await request.formData()).get("name") });
invalidate("app:user");
},
};invalidate("app:user")— hapus tiap page yang loader-nya panggildepends("app:user").invalidate("/api/posts")— hapus tiap page yang loader ataumetadata()-nya fetch/api/posts, plus response API/api/postsyang di-cache.invalidateAll("/products/")— hapus tiap entry yang path-nya diawali prefix.
Nama-namanya cocok dengan invalidate() sisi browser dari bosia/client. Versi server menerapkan konsep key yang sama untuk cache server baru.
Tagging loader
depends() menandai cache loader klien DAN response cache server, jadi satu panggilan melayani dua lapisan:
export async function load({ depends, locals }) {
depends("app:user");
return { user: locals.user };
}Saat form action menjalankan invalidate("app:user"), kedua cache menghapus entry dan GET berikutnya menjalankan ulang load().
Parameter tracking
Link dari iklan, newsletter dan media sosial membawa parameter klik (?utm_source=…, ?fbclid=…, ?gclid=…) yang tidak pernah mengubah halaman. Tiap nilai akan jadi entry cache sendiri — render penuh per klik, dan halaman yang ramai terdorong keluar dari LRU — jadi halaman mencari request seperti itu dengan URL tanpa parameter tersebut: /blog?utm_source=x&page=2 dilayani dari cache /blog?page=2. Loader tetap melihat URL lengkap.
- Daftarnya
CACHE_IGNORE_PARAMS(defaultutm_*,fbclid,gclid,gbraid,wbraid,dclid,msclkid,twclid,ttclid,li_fat_id,igshid,yclid,mc_cid,mc_eid,_ga,_gl).nama*cocok sebagai prefix; mengisinya mengganti daftar;nonemematikannya. - Kalau halaman bersihnya belum ada di cache, request di-cache dengan URL lengkapnya sendiri, seperti sebelumnya. Request ini tidak pernah mengisi entry halaman bersih: komponen bisa merender
page.url(link canonical, fieldgclidtersembunyi), jadi HTML-nya bisa berisi nilai milik pengunjung itu. - Halaman yang
load()ataumetadata()-nya membaca salah satunya (url.searchParams.get("utm_source")) atau seluruh query (url.search,url.href, iterasiurl.searchParams) tidak dilayani salinan bersih untuk request seperti itu — di-cache per URL lengkap, dengan nilai aslinya. - Tidak terlacak: komponen, hook
handle, atau fragmenrenderplugin yang memakai parameternya. Salinan bersih (dirender tanpa parameter) yang dilayani; setCACHE_IGNORE_PARAMS=none, atau baca parameternya diload(). - Hanya halaman dan request data router klien. Route API tetap memakai URL lengkap.
Endpoint API
Handler GET +server.ts di-cache dengan aturan key yang sama. Di v0.6 hanya bisa di-invalidasi berdasarkan URL atau prefix — belum ada mekanisme depends() untuk handler API:
invalidate("/api/posts"); // exact
invalidateAll("/api/"); // prefixInvalidasi berbasis tag untuk endpoint API ada di roadmap.
Env var
| Variabel | Default | Tujuan |
|---|---|---|
CACHE_KEYS |
session,sid,auth,token,jwt,Authorization |
Nama cookie/header yang ikut menghitung identity hash. |
CACHE_MAX_ENTRIES |
500 |
Kapasitas LRU. 0 mematikan cache sepenuhnya. |
CACHE_MAX_BYTES |
104857600 (100MB) |
Memori semua entry (raw + gzip + brotli). 0 = jumlah entry saja. |
CACHE_IGNORE_PARAMS |
utm_*,fbclid,gclid,… |
Parameter query yang tidak masuk key cache halaman. Lihat Parameter tracking. none = mati. |
CACHE_WAIT_TIMEOUT |
60000 |
Ms sebuah miss menunggu request lain yang sedang membangun entry yang sama sebelum membangun sendiri. 0 = tunggu sampai selesai. |
Semuanya dibaca sekali saat startup. Tiap entry menyimpan byte raw plus salinan gzip + brotli — biasanya beberapa KB.
Verifikasi
curl -i https://localhost:9000/ | grep X-Bosia-Cache—HITdi request kedua, kosong di pertama.curl -H 'Accept-Encoding: br' -I—Content-Encoding: brsaat hit.curl -H 'Cookie: session=alice' …laluCookie: session=bob— keduanya miss (identity hash beda).
Trade-off
- Memori tumbuh sampai
CACHE_MAX_BYTES(default 100MB) atauCACHE_MAX_ENTRIESentry, mana yang lebih dulu. Atur sesuai container. - Cache hidup in-process. Replika kedua punya cache sendiri; invalidasi pub/sub multi-replika ada di roadmap.
- Tidak ada expiry berbasis TTL — entry hidup sampai LRU eviction atau
invalidate()eksplisit. Write yang dilakukan author memicu eviction.