Edge runtime fetch dengan cache header
Tune cache header di edge runtime (Vercel, Cloudflare Workers) — s-maxage, stale-while-revalidate, dan tag-based purge. Latency 30ms global.
Dipublikasikan 14 Juni 2026
API endpoint daftar produk Tokopedia di-call ribuan kali per detik. Tanpa cache, server meledak. Pakai edge runtime + cache header yang tepat, hit rate bisa 95% — origin cuma di-hit untuk revalidation. Snippet ini route handler Next.js edge dengan caching strategi lengkap.
Kode
// app/api/produk/[id]/route.ts
import { NextResponse } from "next/server";
// Pin ke edge runtime — eksekusi di POP terdekat user
export const runtime = "edge";
interface Produk {
id: number;
nama: string;
harga: number;
stok: number;
updated_at: string;
}
async function fetchProdukDariOrigin(id: string): Promise<Produk> {
const r = await fetch(`${process.env.ORIGIN_API_URL}/produk/${id}`, {
headers: { "X-Internal-Key": process.env.INTERNAL_KEY! },
});
if (!r.ok) throw new Error(`Origin error: ${r.status}`);
return r.json();
}
export async function GET(
_request: Request,
ctx: { params: Promise<{ id: string }> }
) {
const { id } = await ctx.params;
if (!/^\d+$/.test(id)) {
return NextResponse.json(
{ error: "ID tidak valid" },
{ status: 400, headers: { "Cache-Control": "no-store" } }
);
}
try {
const produk = await fetchProdukDariOrigin(id);
return NextResponse.json(produk, {
headers: {
// Browser cache 1 menit
// CDN cache 5 menit, stale-while-revalidate 1 jam
// Sehingga: hit dalam 5 menit instant
// hit 5-65 menit instant + background refresh
// hit > 65 menit re-fetch origin
"Cache-Control":
"public, max-age=60, s-maxage=300, stale-while-revalidate=3600",
// Cache tag — bisa purge selective via API
"Cache-Tag": `produk-${id},katalog`,
// CDN bisa cache walaupun ada Set-Cookie (Vercel)
"Vercel-CDN-Cache-Control": "max-age=300",
},
});
} catch (err) {
return NextResponse.json(
{ error: "Origin tidak tersedia" },
{
status: 503,
headers: { "Cache-Control": "no-store" },
}
);
}
}
// app/api/cache/purge/route.ts — endpoint internal untuk purge
import { NextResponse } from "next/server";
export const runtime = "edge";
export async function POST(req: Request) {
// Auth: cek secret
const auth = req.headers.get("X-Purge-Secret");
if (auth !== process.env.PURGE_SECRET) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { tag } = (await req.json()) as { tag: string };
// Vercel Cache API
const purgeResp = await fetch(
`https://api.vercel.com/v1/edge-config/${process.env.VERCEL_PROJECT_ID}/purge`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VERCEL_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ tag }),
}
);
if (!purgeResp.ok) {
return NextResponse.json({ error: "Purge fail" }, { status: 502 });
}
return NextResponse.json({ ok: true, tag });
}
Pemakaian
// Client fetch — biarkan browser handle cache via header
const produk = await fetch("/api/produk/123").then((r) => r.json());
// Saat admin update produk, trigger purge
async function updateProduk(id: number, data: Partial<Produk>) {
await fetch(`/api/admin/produk/${id}`, {
method: "PUT",
body: JSON.stringify(data),
});
// Invalidate cache CDN
await fetch("/api/cache/purge", {
method: "POST",
headers: { "X-Purge-Secret": INTERNAL_SECRET },
body: JSON.stringify({ tag: `produk-${id}` }),
});
}
// Bulk purge — saat ada katalog campaign update
await fetch("/api/cache/purge", {
method: "POST",
headers: { "X-Purge-Secret": INTERNAL_SECRET },
body: JSON.stringify({ tag: "katalog" }),
});
// Semua produk dengan tag "katalog" di-evict
Kapan dipakai
- API publik dengan response yang gak sering berubah (katalog produk, list kategori).
- SEO landing page yang content stabil per hari/jam.
- Image proxy / transformation endpoint.
- Public API rate-limited yang banyak diakses dari client.
Catatan
- s-maxage vs max-age — pakai keduanya. s-maxage tinggi (CDN lama), max-age rendah (browser refresh sering biar dapat update setelah purge).
- stale-while-revalidate — magic untuk perceived perf. User dapat data lama 1 detik, sementara CDN refresh di background.
- Cache-Tag — Vercel & Cloudflare support tag-based purge. Lebih clean dari URL-based.
- no-store untuk error — jangan cache 4xx/5xx response. Kalau di-cache, error stuck sampai TTL habis.
- Vary header — kalau response tergantung Accept-Language, Authorization, dll, set
Vary: Authorization, Accept-Language. Tanpa ini, CDN serve response salah ke user beda.
Cache hit rate tinggi cuma penting kalau origin lambat. Untuk endpoint yang sudah cepat (<50ms), overhead CDN bisa lebih besar dari hit langsung. Profile dulu sebelum bertumpuk cache layer.
# tags
edge-runtimecacheswrcdnperformance
Ditulis oleh Asti Larasati · 14 Juni 2026