Edge Functions: Apa Itu, Kapan Menggunakannya, dan Kapan Tidak
Edge runtime, V8 isolates, mitos cold start, geo-routing, A/B testing, auth di edge, dan mengapa saya memindahkan beberapa hal kembali ke Node.js. Pandangan seimbang tentang edge computing.
Kata "edge" sering dilontarkan. Vercel mengatakannya. Cloudflare mengatakannya. Deno mengatakannya. Setiap presentasi konferensi tentang performa web pasti menyebut "berjalan di edge" seolah-olah itu mantra ajaib yang membuat aplikasi Anda cepat.
Saya percaya itu. Saya memindahkan middleware, API route, bahkan beberapa logika rendering ke edge runtime. Beberapa perpindahan itu brilian. Yang lain diam-diam saya pindahkan kembali ke Node.js tiga minggu kemudian setelah men-debug error connection pool jam 2 pagi.
Posting ini adalah versi seimbang dari cerita itu — apa sebenarnya edge itu, di mana ia benar-benar bersinar, di mana ia sama sekali tidak cocok, dan bagaimana saya memutuskan runtime mana yang digunakan untuk setiap bagian aplikasi saya.
Apa Itu Edge?#
Mari mulai dengan geografi. Ketika seseorang mengunjungi website Anda, request mereka berjalan dari perangkat mereka, melalui ISP mereka, melintasi internet ke server Anda, diproses, dan respons berjalan kembali sepanjang jalan. Jika server Anda di us-east-1 (Virginia) dan pengguna Anda di Tokyo, perjalanan pulang-pergi itu mencakup sekitar 14.000 km. Dengan kecepatan cahaya melalui serat optik, itu sekitar 70ms hanya untuk fisikanya — satu arah. Tambahkan resolusi DNS, TLS handshake, dan waktu pemrosesan, dan Anda dengan mudah melihat 200-400ms sebelum pengguna Anda melihat satu byte pun.
"Edge" berarti menjalankan kode Anda di server yang didistribusikan secara global — node CDN yang sama yang selalu menyajikan aset statis, tapi sekarang mereka juga bisa mengeksekusi logika Anda. Alih-alih satu server origin di Virginia, kode Anda berjalan di 300+ lokasi di seluruh dunia. Pengguna di Tokyo menghubungi server di Tokyo. Pengguna di Paris menghubungi server di Paris.
Perhitungan latensi sederhana dan menarik:
Tradisional (origin tunggal):
Tokyo → Virginia: ~140ms perjalanan pulang-pergi (fisika saja)
+ TLS handshake: ~140ms lagi (perjalanan pulang-pergi lagi)
+ Pemrosesan: 20-50ms
Total: ~300-330ms
Edge (PoP lokal):
Tokyo → Node edge Tokyo: ~5ms perjalanan pulang-pergi
+ TLS handshake: ~5ms lagi
+ Pemrosesan: 5-20ms
Total: ~15-30ms
Itu peningkatan 10-20x untuk respons awal. Ini nyata, terukur, dan untuk operasi tertentu sangat transformatif.
Tapi inilah yang dilewatkan marketing: edge bukan lingkungan server penuh. Ini sesuatu yang secara fundamental berbeda.
V8 Isolates vs Node.js#
Node.js tradisional berjalan dalam proses sistem operasi penuh. Ia punya akses ke filesystem, bisa membuka koneksi TCP, bisa menjalankan child process, bisa membaca environment variables sebagai stream, bisa melakukan apa saja yang bisa dilakukan proses Linux.
Edge functions tidak berjalan di Node.js. Mereka berjalan di V8 isolates — mesin JavaScript yang sama yang menggerakkan Chrome, tapi dipangkas ke intinya. Bayangkan V8 isolate sebagai sandbox ringan:
// Ini berfungsi di Node.js tapi TIDAK di edge
import fs from "fs";
import { createConnection } from "net";
import { execSync } from "child_process";
const file = fs.readFileSync("/etc/hosts"); // ❌ Tidak ada filesystem
const conn = createConnection({ port: 5432 }); // ❌ Tidak ada TCP mentah
const result = execSync("ls -la"); // ❌ Tidak ada child processes
process.env.DATABASE_URL; // ⚠️ Tersedia tapi statis, diatur saat deployYang Anda miliki di edge adalah permukaan Web API — API yang sama yang tersedia di browser:
// Semua ini berfungsi di edge
const response = await fetch("https://api.example.com/data");
const url = new URL(request.url);
const headers = new Headers({ "Content-Type": "application/json" });
const encoder = new TextEncoder();
const encoded = encoder.encode("hello");
const hash = await crypto.subtle.digest("SHA-256", encoded);
const id = crypto.randomUUID();
// Web Streams API
const stream = new ReadableStream({
start(controller) {
controller.enqueue("chunk 1");
controller.enqueue("chunk 2");
controller.close();
},
});
// Cache API
const cache = caches.default;
await cache.put(request, response.clone());Batasannya nyata dan keras:
- Memori: 128MB per isolate (Cloudflare Workers), 256MB di beberapa platform
- Waktu CPU: 10-50ms waktu CPU aktual (bukan wall clock time —
await fetch()tidak dihitung, tapiJSON.parse()pada payload 5MB dihitung) - Tanpa modul native: Apa pun yang memerlukan binding C++ (bcrypt, sharp, canvas) tidak bisa
- Tanpa koneksi persisten: Anda tidak bisa mempertahankan koneksi database antar request
- Batas ukuran bundle: Biasanya 1-5MB untuk seluruh skrip worker
Ini bukan Node.js di CDN. Ini runtime yang berbeda dengan model mental yang berbeda.
Cold Starts: Mitos dan Realita#
Anda mungkin pernah dengar bahwa edge functions punya "nol cold start." Ini... sebagian besar benar, dan perbandingannya benar-benar dramatis.
Fungsi serverless tradisional berbasis container (AWS Lambda, Google Cloud Functions) bekerja seperti ini:
- Request datang
- Platform menyediakan container (jika tidak ada yang tersedia)
- Container mem-boot OS
- Runtime diinisialisasi (Node.js, Python, dll.)
- Kode Anda dimuat dan diinisialisasi
- Request diproses
Langkah 2-5 adalah cold start. Untuk Lambda Node.js, ini biasanya 200-500ms. Untuk Lambda Java, bisa 2-5 detik. Untuk Lambda .NET, 500ms-1.5s.
V8 isolates bekerja secara berbeda:
- Request datang
- Platform membuat V8 isolate baru (atau menggunakan kembali yang warm)
- Kode Anda dimuat (sudah dikompilasi ke bytecode saat deploy)
- Request diproses
Langkah 2-3 memakan waktu di bawah 5ms. Sering di bawah 1ms. Isolate bukan container — tidak ada OS untuk di-boot, tidak ada runtime untuk diinisialisasi. V8 membuat isolate baru dalam mikrodetik. Frasa "nol cold start" adalah bahasa marketing, tapi kenyataannya (startup sub-5ms) cukup dekat dengan nol sehingga tidak masalah untuk kebanyakan kasus penggunaan.
Tapi inilah kapan cold start masih menggigit Anda di edge:
Bundle besar. Jika edge function Anda menarik 2MB dependensi, kode itu tetap perlu dimuat dan di-parse. Saya belajar ini dengan cara yang sulit ketika saya mem-bundle pustaka validasi dan pustaka format tanggal ke dalam edge middleware. Cold start naik dari 2ms ke 40ms. Masih cepat, tapi bukan "nol."
Lokasi jarang. Provider edge punya ratusan PoP, tapi tidak semua PoP menjaga kode Anda tetap warm. Jika Anda mendapat satu request per jam dari Nairobi, isolate itu didaur ulang antar request. Request berikutnya membayar biaya startup lagi.
Multiple isolates per request. Jika edge function Anda memanggil edge function lain (atau jika middleware dan API route keduanya edge), Anda mungkin menjalankan beberapa isolate untuk satu user request.
Saran praktis: jaga bundle edge function Anda tetap kecil. Impor hanya apa yang diperlukan. Tree-shake secara agresif. Semakin kecil bundle, semakin cepat cold start, semakin kokoh janji "nol cold start".
// ❌ Jangan lakukan ini di edge
import dayjs from "dayjs";
import * as yup from "yup";
import lodash from "lodash";
// ✅ Lakukan ini — gunakan API bawaan
const date = new Date().toISOString();
const isValid = typeof input === "string" && input.length < 200;
const unique = [...new Set(items)];Kasus Penggunaan Sempurna untuk Edge Functions#
Setelah bereksperimen secara ekstensif, saya menemukan pola yang jelas: edge functions unggul ketika Anda perlu membuat keputusan cepat tentang request sebelum mencapai server origin Anda. Mereka adalah penjaga gerbang, router, dan transformer — bukan server aplikasi.
1. Redirect Berbasis Geolokasi#
Ini adalah kasus penggunaan andalan. Request mengenai node edge terdekat, yang sudah tahu di mana pengguna berada. Tidak perlu panggilan API, tidak perlu database lookup IP — platform menyediakan data geo:
// middleware.ts — berjalan di edge pada setiap request
import { NextRequest, NextResponse } from "next/server";
export const config = {
matcher: ["/", "/shop/:path*"],
};
export function middleware(request: NextRequest) {
const country = request.geo?.country ?? "US";
const city = request.geo?.city ?? "Unknown";
const region = request.geo?.region ?? "Unknown";
// Redirect ke toko spesifik negara
if (request.nextUrl.pathname === "/shop") {
const storeMap: Record<string, string> = {
DE: "/shop/eu",
FR: "/shop/eu",
GB: "/shop/uk",
JP: "/shop/jp",
TR: "/shop/tr",
};
const storePath = storeMap[country] ?? "/shop/us";
if (request.nextUrl.pathname !== storePath) {
return NextResponse.redirect(new URL(storePath, request.url));
}
}
// Tambahkan header geo untuk penggunaan downstream
const response = NextResponse.next();
response.headers.set("x-user-country", country);
response.headers.set("x-user-city", city);
response.headers.set("x-user-region", region);
return response;
}Ini berjalan dalam waktu kurang dari 5ms, tepat di samping pengguna. Alternatifnya — mengirim request jauh ke server origin Anda hanya untuk melakukan IP lookup dan redirect kembali — akan memakan 100-300ms untuk pengguna yang jauh dari origin Anda.
2. A/B Testing Tanpa Kedipan Klien#
A/B testing sisi klien menyebabkan "flash of original content" yang menakutkan — pengguna melihat versi A sekilas sebelum JavaScript menukarnya dengan versi B. Di edge, Anda bisa menetapkan varian sebelum halaman bahkan mulai dirender:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
// Periksa apakah pengguna sudah punya penetapan varian
const existingVariant = request.cookies.get("ab-variant")?.value;
if (existingVariant) {
// Rewrite ke halaman varian yang benar
const url = request.nextUrl.clone();
url.pathname = `/variants/${existingVariant}${url.pathname}`;
return NextResponse.rewrite(url);
}
// Tetapkan varian baru (pembagian 50/50)
const variant = Math.random() < 0.5 ? "control" : "treatment";
const url = request.nextUrl.clone();
url.pathname = `/variants/${variant}${url.pathname}`;
const response = NextResponse.rewrite(url);
response.cookies.set("ab-variant", variant, {
maxAge: 60 * 60 * 24 * 30, // 30 hari
httpOnly: true,
sameSite: "lax",
});
return response;
}Pengguna tidak pernah melihat kedipan karena rewrite terjadi di level jaringan. Browser bahkan tidak tahu itu adalah A/B test — ia hanya menerima halaman varian secara langsung.
3. Verifikasi Token Auth#
Jika auth Anda menggunakan JWT (dan Anda tidak melakukan lookup session database), edge sangat sempurna. Verifikasi JWT adalah kripto murni — tidak perlu database:
import { jwtVerify, importSPKI } from "jose";
import { NextRequest, NextResponse } from "next/server";
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`;
export async function middleware(request: NextRequest) {
const token = request.cookies.get("session-token")?.value;
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
try {
const key = await importSPKI(PUBLIC_KEY, "RS256");
const { payload } = await jwtVerify(token, key, {
algorithms: ["RS256"],
issuer: "https://auth.example.com",
});
// Kirim info pengguna ke downstream sebagai header
const response = NextResponse.next();
response.headers.set("x-user-id", payload.sub as string);
response.headers.set("x-user-role", payload.role as string);
return response;
} catch {
// Token kedaluwarsa atau tidak valid
const response = NextResponse.redirect(new URL("/login", request.url));
response.cookies.delete("session-token");
return response;
}
}Pola ini sangat kuat: edge middleware memverifikasi token dan meneruskan informasi pengguna ke origin Anda sebagai header terpercaya. API route Anda tidak perlu memverifikasi token lagi — cukup baca request.headers.get("x-user-id").
4. Deteksi Bot dan Rate Limiting#
Edge functions bisa memblokir traffic yang tidak diinginkan sebelum pernah mencapai origin Anda:
import { NextRequest, NextResponse } from "next/server";
// Rate limiter in-memory sederhana (per lokasi edge)
const rateLimitMap = new Map<string, { count: number; timestamp: number }>();
export function middleware(request: NextRequest) {
const ip = request.headers.get("x-forwarded-for")?.split(",").pop()?.trim()
?? "unknown";
const ua = request.headers.get("user-agent") ?? "";
// Blokir bot yang diketahui berbahaya
const badBots = ["AhrefsBot", "SemrushBot", "MJ12bot", "DotBot"];
if (badBots.some((bot) => ua.includes(bot))) {
return new NextResponse("Forbidden", { status: 403 });
}
// Rate limiting sederhana
const now = Date.now();
const windowMs = 60_000; // 1 menit
const maxRequests = 100;
const entry = rateLimitMap.get(ip);
if (entry && now - entry.timestamp < windowMs) {
entry.count++;
if (entry.count > maxRequests) {
return new NextResponse("Too Many Requests", {
status: 429,
headers: { "Retry-After": "60" },
});
}
} else {
rateLimitMap.set(ip, { count: 1, timestamp: now });
}
// Pembersihan berkala untuk mencegah memory leak
if (rateLimitMap.size > 10_000) {
const cutoff = now - windowMs;
for (const [key, val] of rateLimitMap) {
if (val.timestamp < cutoff) rateLimitMap.delete(key);
}
}
return NextResponse.next();
}Satu catatan: peta rate limit di atas bersifat per-isolate, per-lokasi. Jika Anda memiliki 300 lokasi edge, masing-masing punya peta sendiri. Untuk rate limiting ketat, Anda memerlukan penyimpanan terdistribusi seperti Upstash Redis atau Cloudflare Durable Objects. Tapi untuk pencegahan penyalahgunaan kasar, batas per-lokasi berfungsi sangat baik.
5. Request Rewriting dan Header Personalisasi#
Edge functions sangat baik dalam mentransformasi request sebelum mencapai origin Anda:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const response = NextResponse.next();
const url = request.nextUrl;
// Negosiasi konten berbasis perangkat
const ua = request.headers.get("user-agent") ?? "";
const isMobile = /mobile|android|iphone/i.test(ua);
response.headers.set("x-device-type", isMobile ? "mobile" : "desktop");
// Feature flags dari cookie
const flags = request.cookies.get("feature-flags")?.value;
if (flags) {
response.headers.set("x-feature-flags", flags);
}
// Deteksi locale untuk i18n
const acceptLanguage = request.headers.get("accept-language") ?? "en";
const preferredLocale = acceptLanguage.split(",")[0]?.split("-")[0] ?? "en";
const supportedLocales = [
"en", "tr", "de", "fr", "es", "pt", "ja", "ko", "it",
"nl", "ru", "pl", "uk", "sv", "cs", "ar", "hi", "zh",
];
const locale = supportedLocales.includes(preferredLocale)
? preferredLocale
: "en";
if (!url.pathname.startsWith(`/${locale}`) && !url.pathname.startsWith("/api")) {
return NextResponse.redirect(new URL(`/${locale}${url.pathname}`, request.url));
}
return response;
}Di Mana Edge Gagal#
Ini adalah bagian yang dilewati halaman marketing. Saya sudah menabrak setiap tembok ini.
1. Koneksi Database#
Ini yang besar. Database tradisional (PostgreSQL, MySQL) menggunakan koneksi TCP persisten. Server Node.js membuka connection pool saat startup dan menggunakan kembali koneksi tersebut di seluruh request. Efisien, terbukti, dipahami dengan baik.
Edge functions tidak bisa melakukan ini. Setiap isolate bersifat ephemeral. Tidak ada fase "startup" di mana Anda membuka koneksi. Bahkan jika Anda bisa membuka koneksi, isolate mungkin didaur ulang setelah satu request, membuang waktu setup koneksi.
// ❌ Pola ini secara fundamental tidak berfungsi di edge
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10, // Connection pool 10
});
// Setiap pemanggilan edge akan:
// 1. Membuat Pool baru (tidak bisa digunakan ulang antar pemanggilan secara reliable)
// 2. Membuka koneksi TCP ke database Anda (yang ada di us-east-1, bukan di edge)
// 3. Melakukan TLS handshake dengan database
// 4. Menjalankan query
// 5. Membuang koneksi ketika isolate didaur ulang
// Bahkan dengan layanan connection pooling seperti PgBouncer,
// Anda tetap membayar latensi jaringan dari edge → database originMasalah perjalanan pulang-pergi database itu fundamental. Database Anda ada di satu region. Edge function Anda ada di 300 region. Setiap query database dari edge harus berjalan dari lokasi edge ke region database dan kembali. Untuk pengguna di Tokyo yang menghubungi node edge Tokyo, tapi database Anda di Virginia:
Edge function di Tokyo
→ Query ke PostgreSQL di Virginia: ~140ms perjalanan pulang-pergi
→ Query kedua: ~140ms lagi
→ Total: 280ms hanya untuk dua query
Fungsi Node.js di Virginia (region yang sama dengan DB)
→ Query ke PostgreSQL: ~1ms perjalanan pulang-pergi
→ Query kedua: ~1ms lagi
→ Total: 2ms untuk dua query
Edge function 140x lebih lambat untuk operasi database dalam skenario ini. Tidak masalah bahwa edge function dimulai lebih cepat — perjalanan pulang-pergi database mendominasi semuanya.
Inilah mengapa proxy database berbasis HTTP ada (driver serverless Neon, driver berbasis fetch PlanetScale, REST API Supabase). Mereka berfungsi, tapi tetap melakukan request HTTP ke database di satu region. Mereka menyelesaikan masalah "tidak bisa menggunakan TCP" tapi bukan masalah "database jauh".
// ✅ Ini berfungsi di edge (akses database berbasis HTTP)
// Tapi tetap lambat jika database jauh dari node edge
import { neon } from "@neondatabase/serverless";
export const runtime = "edge";
export async function GET(request: Request) {
const sql = neon(process.env.DATABASE_URL!);
// Ini membuat request HTTP ke database Neon Anda
// Berfungsi, tapi latensi tergantung jarak ke region database
const posts = await sql`SELECT * FROM posts WHERE published = true LIMIT 10`;
return Response.json(posts);
}2. Tugas Berjalan Lama#
Edge functions memiliki batas waktu CPU, biasanya 10-50ms waktu komputasi aktual. Wall clock time lebih murah hati (biasanya 30 detik), tapi operasi intensif CPU akan mencapai batas dengan cepat:
// ❌ Ini akan melampaui batas waktu CPU di edge
export const runtime = "edge";
export async function POST(request: Request) {
const data = await request.json();
// Pemrosesan gambar — intensif CPU
// (Juga tidak bisa menggunakan sharp karena modul native)
const processed = heavyImageProcessing(data.image);
// Pembuatan PDF — intensif CPU + butuh API Node.js
const pdf = generatePDF(data.content);
// Transformasi data besar
const result = data.items // 100.000 item
.map(transform)
.filter(validate)
.sort(compare)
.reduce(aggregate, {});
return Response.json(result);
}Jika fungsi Anda memerlukan lebih dari beberapa milidetik waktu CPU, tempatnya di server Node.js regional. Titik.
3. Dependensi Khusus Node.js#
Yang ini sering mengejutkan orang. Sejumlah mengejutkan paket npm bergantung pada modul bawaan Node.js:
// ❌ Paket-paket ini tidak akan berfungsi di edge
import bcrypt from "bcrypt"; // Binding C++ native
import sharp from "sharp"; // Binding C++ native
import puppeteer from "puppeteer"; // Butuh filesystem + child_process
import nodemailer from "nodemailer"; // Butuh modul net
import { readFile } from "fs/promises"; // API filesystem Node.js
import mongoose from "mongoose"; // Koneksi TCP + API Node.js
// ✅ Alternatif yang kompatibel dengan edge
import { hashSync } from "bcryptjs"; // Implementasi JS murni (lebih lambat)
// Untuk gambar: gunakan layanan atau API terpisah
// Untuk email: gunakan API email berbasis HTTP (Resend, SendGrid REST)
// Untuk database: gunakan klien berbasis HTTPSebelum memindahkan apa pun ke edge, periksa setiap dependensi. Satu require("fs") yang tersembunyi tiga level di pohon dependensi Anda akan membuat edge function crash saat runtime — bukan saat build time. Anda deploy, semuanya tampak baik, lalu request pertama yang mengenai jalur kode itu dan Anda mendapat error yang sulit dipahami.
4. Ukuran Bundle Besar#
Platform edge memiliki batas ukuran bundle yang ketat:
- Cloudflare Workers: 1MB (gratis), 5MB (berbayar)
- Vercel Edge Functions: 4MB (terkompresi)
- Deno Deploy: 20MB
Ini terdengar banyak sampai Anda import pustaka komponen UI, pustaka validasi, dan pustaka tanggal. Saya pernah punya edge middleware yang membengkak ke 3.5MB karena saya mengimpor dari barrel file yang menarik seluruh direktori @/components.
// ❌ Import barrel file bisa menarik terlalu banyak
import { validateEmail } from "@/lib/utils";
// Jika utils.ts re-export dari 20 modul lain, semuanya ikut ter-bundle
// ✅ Import langsung dari sumbernya
import { validateEmail } from "@/lib/validators/email";5. Streaming dan WebSockets#
Edge functions bisa melakukan streaming response (Web Streams API), tapi koneksi WebSocket yang bertahan lama adalah cerita berbeda. Meskipun beberapa platform mendukung WebSockets di edge (Cloudflare Workers, Deno Deploy), sifat ephemeral edge functions membuatnya kurang cocok untuk koneksi stateful yang bertahan lama.
Edge Runtime Next.js#
Next.js membuatnya mudah untuk memilih edge runtime per-route. Anda tidak harus all-in — Anda memilih persis route mana yang berjalan di edge.
Middleware (Selalu Edge)#
Middleware Next.js selalu berjalan di edge. Ini by design — middleware mencegat setiap request yang cocok, jadi perlu cepat dan didistribusikan secara global:
// middleware.ts — selalu berjalan di edge, tidak perlu opt-in
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
// Ini berjalan sebelum setiap request yang cocok
// Jaga tetap cepat — tanpa panggilan database, tanpa komputasi berat
return NextResponse.next();
}
export const config = {
// Hanya jalankan di path tertentu
matcher: [
"/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)",
],
};API Routes di Edge#
Setiap route handler bisa memilih edge runtime:
// app/api/hello/route.ts
export const runtime = "edge"; // Satu baris ini mengubah runtime
export async function GET(request: Request) {
return Response.json({
message: "Halo dari edge",
region: process.env.VERCEL_REGION ?? "unknown",
timestamp: Date.now(),
});
}Page Routes di Edge#
Bahkan seluruh halaman bisa dirender di edge, meskipun saya akan berpikir hati-hati sebelum melakukan ini:
// app/dashboard/page.tsx
export const runtime = "edge";
export default async function DashboardPage() {
// Ingat: tidak ada API Node.js di sini
// Pengambilan data harus menggunakan fetch() atau klien yang kompatibel edge
const data = await fetch("https://api.example.com/dashboard", {
headers: { Authorization: `Bearer ${process.env.API_KEY}` },
next: { revalidate: 60 },
}).then((r) => r.json());
return (
<main>
<h1>Dashboard</h1>
{/* render data */}
</main>
);
}Apa yang Tersedia di Edge Runtime#
Berikut referensi praktis tentang apa yang bisa dan tidak bisa Anda gunakan:
// ✅ Tersedia di edge
fetch() // Request HTTP
Request / Response // Request/response standar web
Headers // Header HTTP
URL / URLSearchParams // Parsing URL
TextEncoder / TextDecoder // Encoding string
crypto.subtle // Operasi kripto (signing, hashing)
crypto.randomUUID() // Generasi UUID
crypto.getRandomValues() // Angka acak kriptografis
structuredClone() // Deep cloning
atob() / btoa() // Encoding/decoding Base64
setTimeout() / setInterval() // Timer (tapi ingat batas CPU)
console.log() // Logging
ReadableStream / WritableStream // Streaming
AbortController / AbortSignal // Pembatalan request
URLPattern // Pencocokan pola URL
// ❌ TIDAK tersedia di edge
require() // CommonJS (gunakan import)
fs / path / os // Modul bawaan Node.js
process.exit() // Kontrol proses
Buffer // Gunakan Uint8Array sebagai gantinya
__dirname / __filename // Gunakan import.meta.url
setImmediate() // Bukan standar webAuth di Edge: Pola Lengkap#
Saya ingin membahas lebih dalam tentang autentikasi karena ini salah satu kasus penggunaan edge yang paling berdampak, tapi juga mudah salah.
Pola yang berhasil adalah: verifikasi token di edge, kirim klaim terpercaya ke downstream, jangan pernah sentuh database di middleware.
// lib/edge-auth.ts — Utilitas auth yang kompatibel edge
import { jwtVerify, SignJWT, importSPKI, importPKCS8 } from "jose";
const PUBLIC_KEY_PEM = process.env.JWT_PUBLIC_KEY!;
const ISSUER = "https://auth.myapp.com";
const AUDIENCE = "https://myapp.com";
export interface TokenPayload {
sub: string;
email: string;
role: "user" | "admin" | "moderator";
iat: number;
exp: number;
}
export async function verifyToken(token: string): Promise<TokenPayload | null> {
try {
const publicKey = await importSPKI(PUBLIC_KEY_PEM, "RS256");
const { payload } = await jwtVerify(token, publicKey, {
algorithms: ["RS256"],
issuer: ISSUER,
audience: AUDIENCE,
clockTolerance: 30, // 30 detik toleransi perbedaan jam
});
return payload as unknown as TokenPayload;
} catch {
return null;
}
}
export function isTokenExpiringSoon(payload: TokenPayload): boolean {
const now = Math.floor(Date.now() / 1000);
const fiveMinutes = 5 * 60;
return payload.exp - now < fiveMinutes;
}// middleware.ts — Middleware auth
import { NextRequest, NextResponse } from "next/server";
import { verifyToken, isTokenExpiringSoon } from "./lib/edge-auth";
const PUBLIC_PATHS = ["/", "/login", "/register", "/api/auth/login"];
const ADMIN_PATHS = ["/admin"];
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Lewati auth untuk path publik
if (PUBLIC_PATHS.some((p) => pathname === p || pathname.startsWith("/api/public"))) {
return NextResponse.next();
}
// Ekstrak token
const token = request.cookies.get("auth-token")?.value;
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
// Verifikasi token (kripto murni — tanpa panggilan database)
const payload = await verifyToken(token);
if (!payload) {
const response = NextResponse.redirect(new URL("/login", request.url));
response.cookies.delete("auth-token");
return response;
}
// Kontrol akses berbasis peran
if (ADMIN_PATHS.some((p) => pathname.startsWith(p)) && payload.role !== "admin") {
return NextResponse.redirect(new URL("/unauthorized", request.url));
}
// Kirim info pengguna yang terverifikasi ke origin sebagai header terpercaya
const response = NextResponse.next();
response.headers.set("x-user-id", payload.sub);
response.headers.set("x-user-email", payload.email);
response.headers.set("x-user-role", payload.role);
// Sinyal jika token perlu di-refresh
if (isTokenExpiringSoon(payload)) {
response.headers.set("x-token-refresh", "true");
}
return response;
}// app/api/profile/route.ts — Server origin membaca header terpercaya
export async function GET(request: Request) {
// Header ini diatur oleh edge middleware setelah verifikasi JWT
// Mereka terpercaya karena berasal dari infrastruktur kita sendiri
const userId = request.headers.get("x-user-id");
const userRole = request.headers.get("x-user-role");
if (!userId) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
// Sekarang kita bisa menghubungi database — kita di server origin,
// tepat di samping database, dengan connection pool
const user = await db.user.findUnique({ where: { id: userId } });
return Response.json(user);
}Wawasan kuncinya: edge melakukan bagian cepat (verifikasi kripto), dan origin melakukan bagian lambat (query database). Masing-masing berjalan di tempat yang paling efisien.
Satu catatan penting: ini hanya berfungsi untuk JWT. Jika sistem auth Anda memerlukan lookup database di setiap request (seperti auth berbasis session dengan cookie session ID), edge tidak bisa membantu — Anda tetap perlu menghubungi database, yang berarti perjalanan pulang-pergi ke region origin.
Edge Caching#
Caching di edge adalah di mana hal-hal menjadi menarik. Node edge bisa men-cache response, yang berarti request berikutnya ke URL yang sama dilayani langsung dari edge tanpa menghubungi origin sama sekali.
Cache-Control yang Benar#
// app/api/products/route.ts
export const runtime = "edge";
export async function GET(request: Request) {
const url = new URL(request.url);
const category = url.searchParams.get("category") ?? "all";
const products = await fetch(
`${process.env.ORIGIN_API}/products?category=${category}`,
).then((r) => r.json());
return Response.json(products, {
headers: {
// Cache di CDN selama 60 detik
// Sajikan stale sambil memvalidasi ulang hingga 5 menit
// Klien bisa cache selama 10 detik
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=300, max-age=10",
// Vary berdasarkan header ini agar varian berbeda mendapat entri cache berbeda
Vary: "Accept-Language, Accept-Encoding",
// Tag cache khusus CDN untuk invalidasi terarah
"Cache-Tag": `products,category-${category}`,
},
});
}Pola stale-while-revalidate sangat kuat di edge. Inilah yang terjadi:
- Request pertama: Edge mengambil dari origin, men-cache response, mengembalikannya
- Request dalam 60 detik: Edge melayani dari cache (0ms latensi origin)
- Request pada 61-360 detik: Edge melayani versi stale yang di-cache langsung, tapi mengambil versi segar dari origin di latar belakang
- Setelah 360 detik: Cache sepenuhnya kedaluwarsa, request berikutnya pergi ke origin
Pengguna Anda hampir selalu mendapat response yang di-cache. Trade-off kesegaran bersifat eksplisit dan bisa diatur.
Edge Config untuk Konfigurasi Dinamis#
Vercel Edge Config (dan layanan serupa dari platform lain) memungkinkan Anda menyimpan konfigurasi key-value yang direplikasi ke setiap lokasi edge. Ini sangat berguna untuk feature flags, aturan redirect, dan konfigurasi A/B test yang ingin Anda perbarui tanpa redeploy:
import { get } from "@vercel/edge-config";
import { NextRequest, NextResponse } from "next/server";
export async function middleware(request: NextRequest) {
// Pembacaan Edge Config sangat cepat (~1ms) karena
// data direplikasi ke setiap lokasi edge
const maintenanceMode = await get<boolean>("maintenance_mode");
if (maintenanceMode) {
return NextResponse.rewrite(new URL("/maintenance", request.url));
}
// Feature flags
const features = await get<Record<string, boolean>>("feature_flags");
if (features?.["new_pricing_page"] && request.nextUrl.pathname === "/pricing") {
return NextResponse.rewrite(new URL("/pricing-v2", request.url));
}
// Redirect dinamis (perbarui redirect tanpa redeploy)
const redirects = await get<Array<{ from: string; to: string; permanent: boolean }>>(
"redirects",
);
if (redirects) {
const match = redirects.find((r) => r.from === request.nextUrl.pathname);
if (match) {
return NextResponse.redirect(
new URL(match.to, request.url),
match.permanent ? 308 : 307,
);
}
}
return NextResponse.next();
}Ini benar-benar mengubah permainan. Sebelum Edge Config, mengubah feature flag berarti perubahan kode dan redeploy. Sekarang Anda memperbarui nilai JSON di dashboard dan menyebar secara global dalam hitungan detik.
Perhitungan Performa Sebenarnya#
Mari lakukan perhitungan jujur alih-alih perhitungan marketing. Saya akan membandingkan tiga arsitektur untuk endpoint API tipikal yang perlu melakukan query database:
Skenario: API Profil Pengguna (2 query database)#
Arsitektur A: Node.js Regional Tradisional
Pengguna di Tokyo → Origin di Virginia: 140ms
+ Query DB 1 (region yang sama): 2ms
+ Query DB 2 (region yang sama): 2ms
+ Pemrosesan: 5ms
= Total: ~149ms
Arsitektur B: Edge Function dengan Database HTTP
Pengguna di Tokyo → Edge di Tokyo: 5ms
+ Query DB 1 (HTTP ke Virginia): 145ms
+ Query DB 2 (HTTP ke Virginia): 145ms
+ Pemrosesan: 3ms
= Total: ~298ms ← LEBIH LAMBAT dari regional
Arsitektur C: Edge Function dengan Database Regional (read replica)
Pengguna di Tokyo → Edge di Tokyo: 5ms
+ Query DB 1 (HTTP ke replika Tokyo): 8ms
+ Query DB 2 (HTTP ke replika Tokyo): 8ms
+ Pemrosesan: 3ms
= Total: ~24ms ← Tercepat, tapi butuh DB multi-region
Arsitektur D: Edge untuk Auth + Regional untuk Data
Pengguna di Tokyo → Edge middleware di Tokyo: 5ms (verifikasi JWT)
→ Origin di Virginia: 140ms
+ Query DB 1 (region yang sama): 2ms
+ Query DB 2 (region yang sama): 2ms
+ Pemrosesan: 5ms
= Total: ~154ms
(Tapi auth sudah terverifikasi — origin tidak perlu memverifikasi ulang)
(Dan request tidak sah diblokir di edge — tidak pernah mencapai origin)
Kesimpulannya:
- Edge + database origin = sering lebih lambat dari hanya menggunakan server regional
- Edge + database multi-region = tercepat tapi paling mahal dan kompleks
- Edge untuk penjagaan + regional untuk data = keseimbangan pragmatis terbaik
- Edge murni (tanpa database) = tak terkalahkan untuk hal seperti redirect dan pengecekan auth
Arsitektur D adalah yang saya gunakan untuk sebagian besar proyek. Edge menangani apa yang menjadi keunggulannya (keputusan cepat, auth, routing), dan server Node.js regional menangani keunggulannya (query database, komputasi berat).
Ketika Edge Benar-benar Menang: Operasi Tanpa Database#
Perhitungannya sepenuhnya terbalik ketika tidak ada database yang terlibat:
Redirect (edge):
Pengguna di Tokyo → Edge di Tokyo → respons redirect: ~5ms
Redirect (regional):
Pengguna di Tokyo → Origin di Virginia → respons redirect: ~280ms
Respons API statis (edge + cache):
Pengguna di Tokyo → Edge di Tokyo → respons yang di-cache: ~5ms
Respons API statis (regional):
Pengguna di Tokyo → Origin di Virginia → respons: ~280ms
Pemblokiran bot (edge):
Bot jahat di mana saja → Edge (terdekat) → respons 403: ~5ms
(Bot tidak pernah mencapai server origin Anda)
Pemblokiran bot (regional):
Bot jahat di mana saja → Origin di Virginia → respons 403: ~280ms
(Bot tetap mengonsumsi sumber daya origin)
Untuk operasi yang tidak memerlukan database, edge 20-50x lebih cepat. Ini bukan marketing — ini fisika.
Kerangka Keputusan Saya#
Setelah setahun bekerja dengan edge functions di produksi, berikut diagram alur yang saya gunakan untuk setiap endpoint atau logika baru:
Langkah 1: Apakah memerlukan API Node.js?#
Jika mengimpor fs, net, child_process, atau modul native — Node.js regional. Tidak perlu debat.
Langkah 2: Apakah memerlukan query database?#
Jika ya, dan Anda tidak punya read replica dekat pengguna — Node.js regional (di region yang sama dengan database Anda). Perjalanan pulang-pergi database akan mendominasi.
Jika ya, dan Anda punya read replica yang didistribusikan secara global — Edge bisa berfungsi, menggunakan klien database berbasis HTTP.
Langkah 3: Apakah ini keputusan tentang request (routing, auth, redirect)?#
Jika ya — Edge. Ini adalah sweet spot. Anda membuat keputusan cepat yang menentukan apa yang terjadi pada request sebelum mencapai origin.
Langkah 4: Apakah respons bisa di-cache?#
Jika ya — Edge dengan header Cache-Control yang tepat. Meskipun request pertama pergi ke origin Anda, request berikutnya dilayani dari cache edge.
Langkah 5: Apakah intensif CPU?#
Jika melibatkan komputasi signifikan (pemrosesan gambar, pembuatan PDF, transformasi data besar) — Node.js regional.
Langkah 6: Seberapa sensitif terhadap latensi?#
Jika ini background job atau webhook — Node.js regional. Tidak ada yang menunggunya. Jika ini request user-facing di mana setiap ms penting — Edge, jika memenuhi kriteria lain.
Lembar Contekan#
// ✅ SEMPURNA untuk edge
// - Middleware (auth, redirect, rewrite, header)
// - Logika geolokasi
// - Penetapan A/B test
// - Deteksi bot / aturan WAF
// - Respons API yang ramah cache
// - Pengecekan feature flag
// - Respons preflight CORS
// - Transformasi data statis (tanpa DB)
// - Verifikasi tanda tangan webhook
// ❌ TETAP di Node.js regional
// - Operasi CRUD database
// - Upload / pemrosesan file
// - Manipulasi gambar
// - Pembuatan PDF
// - Pengiriman email (gunakan HTTP API, tapi tetap regional)
// - Server WebSocket
// - Background jobs / antrian
// - Apa pun yang menggunakan paket npm native
// - Halaman SSR dengan query database
// - Resolver GraphQL yang menghubungi database
// 🤔 TERGANTUNG
// - Autentikasi (edge untuk JWT, regional untuk session-DB)
// - API routes (edge jika tanpa DB, regional jika ada DB)
// - Halaman server-rendered (edge jika data dari cache/fetch, regional jika DB)
// - Fitur real-time (edge untuk auth awal, regional untuk koneksi persisten)Yang Sebenarnya Saya Jalankan di Edge#
Untuk situs ini, berikut pembagiannya:
Edge (middleware):
- Deteksi dan redirect locale
- Penyaringan bot
- Header keamanan (CSP, HSTS, dll.)
- Logging akses
- Rate limiting (dasar)
Node.js regional:
- Rendering konten blog (pemrosesan MDX memerlukan API Node.js melalui Velite)
- API routes yang menghubungi Redis
- Pembuatan OG image (memerlukan lebih banyak waktu CPU)
- Pembuatan RSS feed
Statis (tanpa runtime sama sekali):
- Halaman tool (pre-rendered saat build time)
- Halaman posting blog (pre-rendered saat build time)
- Semua gambar dan aset (disajikan CDN)
Runtime terbaik sering kali tanpa runtime. Jika Anda bisa pre-render sesuatu saat build time dan menyajikannya sebagai aset statis, itu akan selalu lebih cepat dari edge function manapun. Edge adalah untuk hal-hal yang benar-benar perlu dinamis di setiap request.
Ringkasan Jujur#
Edge functions bukan pengganti server tradisional. Mereka pelengkap. Mereka adalah alat tambahan di kotak peralatan arsitektur Anda — yang sangat kuat untuk kasus penggunaan yang tepat dan secara aktif merugikan untuk yang salah.
Heuristik yang terus saya kembalikan: jika fungsi Anda perlu menghubungi database di satu region, menempatkan fungsi di edge tidak membantu — itu merugikan. Anda baru saja menambahkan hop. Fungsi berjalan lebih cepat, tapi kemudian menghabiskan 100ms+ untuk kembali ke database. Hasil bersih: lebih lambat dari menjalankan semuanya di satu region.
Tapi untuk keputusan yang bisa dibuat hanya dengan informasi di request itu sendiri — geolokasi, cookie, header, JWT — edge tak terkalahkan. Respons edge 5ms itu bukan benchmark sintetis. Mereka nyata, dan pengguna Anda merasakan perbedaannya.
Jangan pindahkan semuanya ke edge. Jangan jauhkan semuanya dari edge. Tempatkan setiap bagian logika di mana fisika mendukungnya.