İçeriğe geç
·15 dk okuma

Edge Fonksiyonları: Ne Oldukları, Ne Zaman Kullanılmalı, Ne Zaman Kullanılmamalı

Edge runtime, V8 izolatları, soğuk başlatma miti, coğrafi yönlendirme, A/B testi, edge'de kimlik doğrulama ve bazı şeyleri neden Node.js'e geri taşıdım. Edge bilişime dengeli bir bakış.

Paylaş:X / TwitterLinkedIn

"Edge" kelimesi çok fazla kullanılıyor. Vercel söylüyor. Cloudflare söylüyor. Deno söylüyor. Web performansı hakkındaki her konferans konuşması kaçınılmaz olarak "edge'de çalıştırmak"tan bahsediyor, sanki uygulamanızı hızlandıran sihirli bir büyüymüş gibi.

Ben de buna inandım. Middleware'leri, API rotalarını, hatta bazı render mantıklarını edge runtime'a taşıdım. Bu hamlelerden bazıları parlaktı. Diğerlerini ise gece saat 2'de bağlantı havuzu hatalarını debug ettikten sonra sessizce üç hafta içinde Node.js'e geri taşıdım.

Bu yazı, o hikayenin dengeli versiyonu -- edge'in gerçekte ne olduğu, gerçekten nerede parlak olduğu, kesinlikle nerede parlamadığı ve uygulamamın her bir parçası için hangi runtime'u kullanacağıma nasıl karar verdiğim.

Edge Nedir?#

Coğrafyayla başlayalım. Birisi web sitenizi ziyaret ettiğinde, istekleri cihazlarından, ISP'leri üzerinden, internet üzerinden sunucunuza ulaşır, işlenir ve yanıt tüm yolu geri kat eder. Sunucunuz us-east-1'de (Virginia) ve kullanıcınız Tokyo'daysa, bu gidiş-dönüş yaklaşık 14.000 km'yi kapsar. Fiber üzerinden ışık hızında bu, tek yön için yaklaşık 70ms eder -- sadece fizik. DNS çözümleme, TLS el sıkışma ve herhangi bir işlem süresini ekleyin, kullanıcınız tek bir byte görmeden kolayca 200-400ms'yi bulursunuz.

"Edge", kodunuzu küresel olarak dağıtılmış sunucularda çalıştırmak demektir -- her zaman statik varlıkları sunan aynı CDN düğümleri, ama artık mantığınızı da çalıştırabiliyorlar. Virginia'da tek bir kaynak sunucu yerine, kodunuz dünya çapında 300+ konumda çalışır. Tokyo'daki bir kullanıcı Tokyo'daki sunucuya ulaşır. Paris'teki bir kullanıcı Paris'teki sunucuya ulaşır.

Gecikme matematiği basit ve ikna edici:

Geleneksel (tek kaynak):
  Tokyo → Virginia: ~140ms gidiş-dönüş (yalnızca fizik)
  + TLS el sıkışma: ~140ms daha (bir gidiş-dönüş daha)
  + İşleme: 20-50ms
  Toplam: ~300-330ms

Edge (yerel PoP):
  Tokyo → Tokyo edge düğümü: ~5ms gidiş-dönüş
  + TLS el sıkışma: ~5ms daha
  + İşleme: 5-20ms
  Toplam: ~15-30ms

Bu, ilk yanıt için 10-20x'lik bir iyileştirme. Gerçek, ölçülebilir ve belirli işlemler için dönüştürücü.

Ama pazarlamanın üzerinden geçtiği şey şu: edge tam bir sunucu ortamı değil. Temelden farklı bir şey.

V8 İzolatları vs Node.js#

Geleneksel Node.js tam bir işletim sistemi sürecinde çalışır. Dosya sistemine erişimi vardır, TCP bağlantıları açabilir, alt süreçler oluşturabilir, ortam değişkenlerini bir akış olarak okuyabilir, esasen bir Linux sürecinin yapabileceği her şeyi yapabilir.

Edge fonksiyonları Node.js'de çalışmaz. V8 izolatlarında çalışırlar -- Chrome'u güçlendiren aynı JavaScript motoru, ama özüne kadar sadeleştirilmiş. V8 izolatını hafif bir sandbox olarak düşünün:

typescript
// Bu Node.js'de çalışır ama edge'de ÇALIŞMAZ
import fs from "fs";
import { createConnection } from "net";
import { execSync } from "child_process";
 
const file = fs.readFileSync("/etc/hosts");        // ❌ Dosya sistemi yok
const conn = createConnection({ port: 5432 });     // ❌ Ham TCP yok
const result = execSync("ls -la");                  // ❌ Alt süreçler yok
process.env.DATABASE_URL;                           // ⚠️  Mevcut ama statik, dağıtım zamanında ayarlanır

Edge'de sahip olduğunuz şey Web API yüzeyi -- tarayıcıda bulunan aynı API'ler:

typescript
// Bunların hepsi edge'de çalışır
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());

Kısıtlamalar gerçek ve sert:

  • Bellek: İzolat başına 128MB (Cloudflare Workers), bazı platformlarda 256MB
  • CPU süresi: 10-50ms gerçek CPU süresi (duvar saati süresi değil -- await fetch() sayılmaz, ama 5MB'lık bir veri üzerinde JSON.parse() sayılır)
  • Yerel modüller yok: C++ bağlaması gerektiren her şey (bcrypt, sharp, canvas) dışarıda
  • Kalıcı bağlantılar yok: İstekler arasında bir veritabanı bağlantısını açık tutamazsınız
  • Bundle boyutu limitleri: Genellikle tüm worker scripti için 1-5MB

Bu, CDN'de Node.js değil. Farklı bir zihinsel modele sahip farklı bir runtime.

Soğuk Başlatmalar: Mit ve Gerçeklik#

Muhtemelen edge fonksiyonlarının "sıfır soğuk başlatmaya" sahip olduğunu duymuşsunuzdur. Bu... çoğunlukla doğru ve karşılaştırma gerçekten dramatik.

Geleneksel konteyner tabanlı sunucusuz bir fonksiyon (AWS Lambda, Google Cloud Functions) şöyle çalışır:

  1. İstek gelir
  2. Platform bir konteyner sağlar (mevcut yoksa)
  3. Konteyner işletim sistemini başlatır
  4. Runtime başlatılır (Node.js, Python, vb.)
  5. Kodunuz yüklenir ve başlatılır
  6. İstek işlenir

2-5 arası soğuk başlatmadır. Node.js Lambda için bu genellikle 200-500ms'dir. Java Lambda için 2-5 saniye olabilir. .NET Lambda için 500ms-1.5s.

V8 izolatları farklı çalışır:

  1. İstek gelir
  2. Platform yeni bir V8 izolatı oluşturur (veya sıcak olanı yeniden kullanır)
  3. Kodunuz yüklenir (dağıtım zamanında zaten bytecode'a derlenmiştir)
  4. İstek işlenir

2-3 arası 5ms'nin altında sürer. Genellikle 1ms'nin altında. İzolat bir konteyner değildir -- başlatılacak bir işletim sistemi yok, başlatılacak bir runtime yok. V8 mikrosaniyeler içinde yeni bir izolat oluşturur. "Sıfır soğuk başlatma" ifadesi pazarlama dilidir, ama gerçeklik (5ms altı başlatma) çoğu kullanım durumu için sıfıra yeterince yakındır.

Ama soğuk başlatmaların edge'de sizi hala ısırdığı durumlar:

Büyük bundle'lar. Edge fonksiyonunuz 2MB bağımlılık çekiyorsa, bu kodun hala yüklenmesi ve ayrıştırılması gerekir. Bunu bir doğrulama kütüphanesi ve tarih biçimlendirme kütüphanesini edge middleware'ine dahil ettiğimde zor yoldan öğrendim. Soğuk başlatma 2ms'den 40ms'ye çıktı. Hala hızlı, ama "sıfır" değil.

Nadir konumlar. Edge sağlayıcılarının yüzlerce PoP'u var, ama tüm PoP'lar kodunuzu sıcak tutmaz. Nairobi'den saatte bir istek alıyorsanız, o izolat istekler arasında geri dönüştürülür. Sonraki istek tekrar başlatma maliyetini öder.

İstek başına birden fazla izolat. Edge fonksiyonunuz başka bir edge fonksiyonunu çağırıyorsa (veya hem middleware hem de API rotası edge ise), tek bir kullanıcı isteği için birden fazla izolat başlatıyor olabilirsiniz.

Pratik tavsiye: edge fonksiyon bundle'larınızı küçük tutun. Yalnızca ihtiyacınız olanı import edin. Agresif bir şekilde tree-shake yapın. Bundle ne kadar küçükse, soğuk başlatma o kadar hızlı olur, "sıfır soğuk başlatma" vaadi de o kadar geçerli olur.

typescript
// ❌ Bunu edge'de yapmayın
import dayjs from "dayjs";
import * as yup from "yup";
import lodash from "lodash";
 
// ✅ Bunun yerine bunu yapın — yerleşik API'leri kullanın
const date = new Date().toISOString();
const isValid = typeof input === "string" && input.length < 200;
const unique = [...new Set(items)];

Edge Fonksiyonları İçin Mükemmel Kullanım Alanları#

Kapsamlı denemelerden sonra net bir kalıp buldum: edge fonksiyonları, bir istek kaynak sunucunuza ulaşmadan önce hızlı bir karar vermeniz gerektiğinde mükemmeldir. Kapı bekçileri, yönlendiriciler ve dönüştürücülerdir -- uygulama sunucuları değil.

1. Coğrafi Konum Tabanlı Yönlendirmeler#

Bu en güçlü kullanım alanıdır. İstek en yakın edge düğümüne ulaşır ve düğüm zaten kullanıcının nerede olduğunu bilir. API çağrısı gerekmez, IP arama veritabanı gerekmez -- platform coğrafi veriyi sağlar:

typescript
// middleware.ts — her istekte edge'de çalışır
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";
 
  // Ülkeye özgü mağazaya yönlendir
  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));
    }
  }
 
  // Alt akış kullanımı için coğrafi başlıklar ekle
  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;
}

Bu 5ms'nin altında, kullanıcının hemen yanında çalışır. Alternatif -- isteği kaynak sunucunuza gönderip sadece IP araması yapıp geri yönlendirmek -- kaynak sunucunuzdan uzak kullanıcılar için 100-300ms'ye mal olur.

2. İstemci Titremesi Olmadan A/B Testi#

İstemci tarafı A/B testi, korkulan "orijinal içeriğin titremesi"ne neden olur -- kullanıcı JavaScript B sürümünü değiştirmeden önce bir anlığına A sürümünü görür. Edge'de, varyantı sayfa render edilmeye başlamadan önce atayabilirsiniz:

typescript
import { NextRequest, NextResponse } from "next/server";
 
export function middleware(request: NextRequest) {
  // Kullanıcının zaten bir varyant ataması var mı kontrol et
  const existingVariant = request.cookies.get("ab-variant")?.value;
 
  if (existingVariant) {
    // Doğru varyant sayfasına yeniden yaz
    const url = request.nextUrl.clone();
    url.pathname = `/variants/${existingVariant}${url.pathname}`;
    return NextResponse.rewrite(url);
  }
 
  // Yeni bir varyant ata (50/50 bölünme)
  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 gün
    httpOnly: true,
    sameSite: "lax",
  });
 
  return response;
}

Kullanıcı hiçbir zaman titreme görmez çünkü yeniden yazma ağ düzeyinde gerçekleşir. Tarayıcı bunun bir A/B testi olduğunu bile bilmez -- sadece varyant sayfasını doğrudan alır.

3. Auth Token Doğrulaması#

Kimlik doğrulamanız JWT kullanıyorsa (ve veritabanı oturum araması yapmıyorsanız), edge mükemmeldir. JWT doğrulama saf kriptodur -- veritabanı gerekmez:

typescript
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",
    });
 
    // Kullanıcı bilgilerini güvenilir başlıklar olarak alt akışa ilet
    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 süresi dolmuş veya geçersiz
    const response = NextResponse.redirect(new URL("/login", request.url));
    response.cookies.delete("session-token");
    return response;
  }
}

Bu kalıp güçlüdür: edge middleware token'ı doğrular ve kullanıcı bilgilerini güvenilir başlıklar olarak kaynağınıza iletir. API rotalarınızın token'ı tekrar doğrulaması gerekmez -- sadece request.headers.get("x-user-id")'yi okurlar.

4. Bot Tespiti ve Hız Sınırlama#

Edge fonksiyonları istenmeyen trafiği kaynak sunucunuza ulaşmadan önce engelleyebilir:

typescript
import { NextRequest, NextResponse } from "next/server";
 
// Basit bellek-içi hız sınırlayıcı (edge konumu başına)
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") ?? "";
 
  // Bilinen kötü botları engelle
  const badBots = ["AhrefsBot", "SemrushBot", "MJ12bot", "DotBot"];
  if (badBots.some((bot) => ua.includes(bot))) {
    return new NextResponse("Forbidden", { status: 403 });
  }
 
  // Basit hız sınırlama
  const now = Date.now();
  const windowMs = 60_000; // 1 dakika
  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 });
  }
 
  // Bellek sızıntısını önlemek için periyodik temizlik
  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();
}

Bir uyarı: yukarıdaki hız sınırı haritası izolat başına, konum başınadır. 300 edge konumunuz varsa, her birinin kendi haritası vardır. Katı hız sınırlama için Upstash Redis veya Cloudflare Durable Objects gibi dağıtılmış bir depolama gerekir. Ama kabaca suistimal önleme için konum başına limitler şaşırtıcı derecede iyi çalışır.

5. İstek Yeniden Yazma ve Kişiselleştirme Başlıkları#

Edge fonksiyonları istekleri kaynak sunucunuza ulaşmadan önce dönüştürmekte mükemmeldir:

typescript
import { NextRequest, NextResponse } from "next/server";
 
export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  const url = request.nextUrl;
 
  // Cihaz tabanlı içerik müzakeresi
  const ua = request.headers.get("user-agent") ?? "";
  const isMobile = /mobile|android|iphone/i.test(ua);
  response.headers.set("x-device-type", isMobile ? "mobile" : "desktop");
 
  // Çerezden özellik bayrakları
  const flags = request.cookies.get("feature-flags")?.value;
  if (flags) {
    response.headers.set("x-feature-flags", flags);
  }
 
  // i18n için dil tespiti
  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;
}

Edge'in Başarısız Olduğu Yerler#

Bu, pazarlama sayfalarının atladığı bölüm. Bu duvarların her birine çarptım.

1. Veritabanı Bağlantıları#

Büyük sorun bu. Geleneksel veritabanları (PostgreSQL, MySQL) kalıcı TCP bağlantıları kullanır. Node.js sunucusu başlangıçta bir bağlantı havuzu açar ve bu bağlantıları istekler arasında yeniden kullanır. Verimli, kanıtlanmış, iyi anlaşılmış.

Edge fonksiyonları bunu yapamaz. Her izolat geçicidir. Bağlantı açtığınız bir "başlangıç" aşaması yoktur. Bağlantı açabilseniz bile, izolat bir istekten sonra geri dönüştürülebilir ve bağlantı kurulum süresini boşa harcayabilir.

typescript
// ❌ Bu kalıp temelden edge'de çalışmaz
import { Pool } from "pg";
 
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 10, // 10'luk bağlantı havuzu
});
 
// Her edge çağrısı:
// 1. Yeni bir Pool oluşturur (çağrılar arasında güvenilir şekilde yeniden kullanılamaz)
// 2. Veritabanınıza TCP bağlantısı açar (us-east-1'de, edge'de değil)
// 3. Veritabanıyla TLS el sıkışması yapar
// 4. Sorguyu çalıştırır
// 5. İzolat geri dönüştürüldüğünde bağlantıyı atar
//
// PgBouncer gibi bağlantı havuzu servisleriyle bile,
// edge → kaynak veritabanı ağ gecikmesini ödüyorsunuz

Veritabanı gidiş-dönüş problemi temeldir. Veritabanınız tek bir bölgededir. Edge fonksiyonunuz 300 bölgededir. Edge'den yapılan her veritabanı sorgusu, edge konumundan veritabanı bölgesine ve geri gitmek zorundadır. Tokyo'daki bir kullanıcı Tokyo edge düğümüne ulaşıyorsa, ama veritabanınız Virginia'daysa:

Tokyo'daki edge fonksiyonu
  → Virginia'daki PostgreSQL'e sorgu: ~140ms gidiş-dönüş
  → İkinci sorgu: ~140ms daha
  → Toplam: iki sorgu için sadece 280ms

Virginia'daki Node.js fonksiyonu (veritabanıyla aynı bölge)
  → PostgreSQL'e sorgu: ~1ms gidiş-dönüş
  → İkinci sorgu: ~1ms daha
  → Toplam: iki sorgu için 2ms

Edge fonksiyonu bu senaryoda veritabanı işlemleri için 140 kat daha yavaş. Edge fonksiyonunun daha hızlı başlaması önemli değil -- veritabanı gidiş-dönüşleri her şeye hakim.

Bu yüzden HTTP tabanlı veritabanı proxy'leri var (Neon'un sunucusuz sürücüsü, PlanetScale'in fetch tabanlı sürücüsü, Supabase'in REST API'si). Çalışıyorlar, ama yine de tek bir bölgedeki veritabanına HTTP istekleri yapıyorlar. "TCP kullanamıyorum" sorununu çözüyorlar ama "veritabanı uzakta" sorununu çözmüyorlar.

typescript
// ✅ Bu edge'de çalışır (HTTP tabanlı veritabanı erişimi)
// Ama veritabanı edge düğümünden uzaksa yine de yavaş
import { neon } from "@neondatabase/serverless";
 
export const runtime = "edge";
 
export async function GET(request: Request) {
  const sql = neon(process.env.DATABASE_URL!);
  // Bu, Neon veritabanınıza bir HTTP isteği yapar
  // Çalışır, ama gecikme veritabanı bölgesine olan mesafeye bağlı
  const posts = await sql`SELECT * FROM posts WHERE published = true LIMIT 10`;
  return Response.json(posts);
}

2. Uzun Süren İşlemler#

Edge fonksiyonlarının CPU süresi limitleri vardır, genellikle 10-50ms gerçek hesaplama süresi. Duvar saati süresi daha cömerttir (genellikle 30 saniye), ama CPU-yoğun işlemler limite hızla ulaşır:

typescript
// ❌ Bunlar edge'de CPU süresi limitlerini aşar
export const runtime = "edge";
 
export async function POST(request: Request) {
  const data = await request.json();
 
  // Görüntü işleme — CPU yoğun
  // (Ayrıca sharp kullanamaz çünkü yerel bir modül)
  const processed = heavyImageProcessing(data.image);
 
  // PDF oluşturma — CPU yoğun + Node.js API'leri gerektirir
  const pdf = generatePDF(data.content);
 
  // Büyük veri dönüşümü
  const result = data.items // 100.000 öğe
    .map(transform)
    .filter(validate)
    .sort(compare)
    .reduce(aggregate, {});
 
  return Response.json(result);
}

Fonksiyonunuz birkaç milisaniyeden fazla CPU süresi gerektiriyorsa, bölgesel bir Node.js sunucusuna aittir. Nokta.

3. Yalnızca Node.js Bağımlılıkları#

Bu insanları hazırlıksız yakalar. Şaşırtıcı sayıda npm paketi Node.js yerleşik modüllerine bağımlıdır:

typescript
// ❌ Bu paketler edge'de çalışmaz
import bcrypt from "bcrypt";            // Yerel C++ bağlaması
import sharp from "sharp";              // Yerel C++ bağlaması
import puppeteer from "puppeteer";      // Dosya sistemi + child_process gerektirir
import nodemailer from "nodemailer";    // net modülü gerektirir
import { readFile } from "fs/promises"; // Node.js dosya sistemi API'si
import mongoose from "mongoose";         // TCP bağlantıları + Node.js API'leri
 
// ✅ Edge uyumlu alternatifler
import { hashSync } from "bcryptjs";    // Saf JS implementasyonu (daha yavaş)
// Görüntüler için: ayrı bir servis veya API kullanın
// E-posta için: HTTP tabanlı e-posta API'si kullanın (Resend, SendGrid REST)
// Veritabanı için: HTTP tabanlı istemciler kullanın

Herhangi bir şeyi edge'e taşımadan önce, her bağımlılığı kontrol edin. Bağımlılık ağacınızda üç seviye derinlikte gizlenen bir require("fs"), edge fonksiyonunuzu çalışma zamanında çökertir -- derleme zamanında değil. Dağıtırsınız, her şey iyi görünür, sonra ilk istek o kod yoluna ulaşır ve şifreli bir hata alırsınız.

4. Büyük Bundle Boyutları#

Edge platformlarının katı bundle boyutu limitleri vardır:

  • Cloudflare Workers: 1MB (ücretsiz), 5MB (ücretli)
  • Vercel Edge Functions: 4MB (sıkıştırılmış)
  • Deno Deploy: 20MB

Bu, bir UI bileşen kütüphanesi, bir doğrulama kütüphanesi ve bir tarih kütüphanesi import edene kadar bolca gibi görünür. Bir keresinde @/components dizininin tamamını çeken bir barrel dosyasından import ettiğim için 3.5MB'a şişen bir edge middleware'im vardı.

typescript
// ❌ Barrel dosya importları çok fazla şey çekebilir
import { validateEmail } from "@/lib/utils";
// utils.ts 20 başka modülden yeniden dışa aktarım yapıyorsa, hepsi bundle'lanır
 
// ✅ Doğrudan kaynaktan import edin
import { validateEmail } from "@/lib/validators/email";

5. Streaming ve WebSocket'ler#

Edge fonksiyonları streaming yanıtlar yapabilir (Web Streams API), ama uzun ömürlü WebSocket bağlantıları farklı bir konu. Bazı platformlar edge'de WebSocket'leri desteklese de (Cloudflare Workers, Deno Deploy), edge fonksiyonlarının geçici doğası onları durumlu, uzun ömürlü bağlantılar için zayıf bir seçenek yapar.

Next.js Edge Runtime#

Next.js, rota bazında edge runtime'a geçmeyi kolaylaştırır. Tamamen geçmek zorunda değilsiniz -- tam olarak hangi rotaların edge'de çalışacağını seçersiniz.

Middleware (Her Zaman Edge)#

Next.js middleware'i her zaman edge'de çalışır. Bu tasarım gereğidir -- middleware her eşleşen isteği yakalar, bu yüzden hızlı ve küresel olarak dağıtılmış olması gerekir:

typescript
// middleware.ts — her zaman edge'de çalışır, opt-in gerekmez
import { NextRequest, NextResponse } from "next/server";
 
export function middleware(request: NextRequest) {
  // Bu, eşleşen her istekten önce çalışır
  // Hızlı tutun — veritabanı çağrıları yok, ağır hesaplama yok
  return NextResponse.next();
}
 
export const config = {
  // Yalnızca belirli yollarda çalıştır
  matcher: [
    "/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)",
  ],
};

Edge'de API Rotaları#

Herhangi bir rota işleyicisi edge runtime'a geçebilir:

typescript
// app/api/hello/route.ts
export const runtime = "edge"; // Bu tek satır runtime'u değiştirir
 
export async function GET(request: Request) {
  return Response.json({
    message: "Hello from the edge",
    region: process.env.VERCEL_REGION ?? "unknown",
    timestamp: Date.now(),
  });
}

Edge'de Sayfa Rotaları#

Tüm sayfalar bile edge'de render edilebilir, ancak bunu yapmadan önce dikkatlice düşünürdüm:

typescript
// app/dashboard/page.tsx
export const runtime = "edge";
 
export default async function DashboardPage() {
  // Unutmayın: burada Node.js API'leri yok
  // Herhangi bir veri çekme fetch() veya edge uyumlu istemciler kullanmalı
  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>
      {/* veriyi render et */}
    </main>
  );
}

Edge Runtime'da Neler Mevcut#

İşte neyi kullanabilip neyi kullanamayacağınızın pratik bir referansı:

typescript
// ✅ Edge'de mevcut
fetch()                          // HTTP istekleri
Request / Response               // Web standart istek/yanıt
Headers                          // HTTP başlıkları
URL / URLSearchParams            // URL ayrıştırma
TextEncoder / TextDecoder        // String kodlama
crypto.subtle                    // Kripto işlemleri (imzalama, hashleme)
crypto.randomUUID()              // UUID oluşturma
crypto.getRandomValues()         // Kriptografik rastgele sayılar
structuredClone()                // Derin klonlama
atob() / btoa()                  // Base64 kodlama/çözme
setTimeout() / setInterval()     // Zamanlayıcılar (ama CPU limitlerini unutmayın)
console.log()                    // Loglama
ReadableStream / WritableStream  // Streaming
AbortController / AbortSignal    // İstek iptali
URLPattern                       // URL kalıp eşleme
 
// ❌ Edge'de mevcut DEĞİL
require()                        // CommonJS (import kullanın)
fs / path / os                   // Node.js yerleşik modülleri
process.exit()                   // Süreç kontrolü
Buffer                           // Bunun yerine Uint8Array kullanın
__dirname / __filename           // import.meta.url kullanın
setImmediate()                   // Web standardı değil

Edge'de Auth: Tam Kalıp#

Kimlik doğrulamayı daha derinlemesine incelemek istiyorum çünkü en etkili edge kullanım alanlarından biri, ama aynı zamanda yanlış yapmak da kolay.

İşe yarayan kalıp şu: token'ı edge'de doğrula, güvenilir talepleri alt akışa ilet, middleware'de asla veritabanına dokunma.

typescript
// lib/edge-auth.ts — Edge uyumlu auth yardımcıları
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 saniyelik saat sapma toleransı
    });
 
    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;
}
typescript
// middleware.ts — Auth middleware'i
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;
 
  // Genel yollar için auth'u atla
  if (PUBLIC_PATHS.some((p) => pathname === p || pathname.startsWith("/api/public"))) {
    return NextResponse.next();
  }
 
  // Token'ı çıkar
  const token = request.cookies.get("auth-token")?.value;
  if (!token) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
 
  // Token'ı doğrula (saf kripto — veritabanı çağrısı yok)
  const payload = await verifyToken(token);
  if (!payload) {
    const response = NextResponse.redirect(new URL("/login", request.url));
    response.cookies.delete("auth-token");
    return response;
  }
 
  // Rol tabanlı erişim kontrolü
  if (ADMIN_PATHS.some((p) => pathname.startsWith(p)) && payload.role !== "admin") {
    return NextResponse.redirect(new URL("/unauthorized", request.url));
  }
 
  // Doğrulanmış kullanıcı bilgilerini güvenilir başlıklar olarak kaynağa ilet
  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);
 
  // Token yenilenmesi gerekiyorsa sinyal ver
  if (isTokenExpiringSoon(payload)) {
    response.headers.set("x-token-refresh", "true");
  }
 
  return response;
}
typescript
// app/api/profile/route.ts — Kaynak sunucu güvenilir başlıkları okur
export async function GET(request: Request) {
  // Bu başlıklar, JWT doğrulamasından sonra edge middleware tarafından ayarlandı
  // Güvenilirler çünkü kendi altyapımızdan geliyorlar
  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 });
  }
 
  // Artık veritabanına erişebiliriz — kaynak sunucudayız,
  // veritabanının hemen yanındayız, bağlantı havuzuyla
  const user = await db.user.findUnique({ where: { id: userId } });
 
  return Response.json(user);
}

Temel içgörü: edge hızlı kısmı (kripto doğrulama) yapar ve kaynak yavaş kısmı (veritabanı sorguları) yapar. Her biri en verimli olduğu yerde çalışır.

Önemli bir uyarı: bu yalnızca JWT'ler için çalışır. Auth sisteminiz her istekte veritabanı araması gerektiriyorsa (oturum ID çerezi olan oturum tabanlı auth gibi), edge yardımcı olamaz -- yine de veritabanını çağırmanız gerekir, bu da kaynak bölgesine bir gidiş-dönüş anlamına gelir.

Edge Önbelleğe Alma#

Edge'de önbelleğe alma işlerin ilginçleştiği yerdir. Edge düğümleri yanıtları önbelleğe alabilir, bu da aynı URL'ye sonraki isteklerin kaynağınıza ulaşmadan doğrudan edge'den sunulduğu anlamına gelir.

Doğru Cache-Control#

typescript
// 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: {
      // CDN'de 60 saniye önbelleğe al
      // Yeniden doğrulama sırasında 5 dakikaya kadar eski sunma
      // İstemci 10 saniye önbelleğe alabilir
      "Cache-Control": "public, s-maxage=60, stale-while-revalidate=300, max-age=10",
 
      // Bu başlıklara göre değiştir, farklı varyantlar farklı önbellek girdileri alsın
      Vary: "Accept-Language, Accept-Encoding",
 
      // Hedefli geçersiz kılma için CDN'ye özgü önbellek etiketi
      "Cache-Tag": `products,category-${category}`,
    },
  });
}

stale-while-revalidate kalıbı edge'de özellikle güçlüdür. İşte ne olur:

  1. İlk istek: Edge kaynaktan alır, yanıtı önbelleğe alır, döndürür
  2. 60 saniye içindeki istekler: Edge önbellekten sunar (0ms kaynak gecikmesi)
  3. 61-360 saniye arasındaki istek: Edge eski önbelleğe alınmış versiyonu hemen sunar, ama arka planda kaynaktan taze bir versiyon alır
  4. 360 saniyeden sonra: Önbellek tamamen süresi dolmuş, sonraki istek kaynağa gider

Kullanıcılarınız neredeyse her zaman önbelleğe alınmış bir yanıt alır. Tazelik ödünleşimi açık ve ayarlanabilir.

Dinamik Yapılandırma İçin Edge Config#

Vercel'in Edge Config'i (ve diğer platformlardan benzer servisler) her edge konumuna çoğaltılan anahtar-değer yapılandırması depolamanızı sağlar. Bu, yeniden dağıtım yapmadan güncellemek istediğiniz özellik bayrakları, yönlendirme kuralları ve A/B test yapılandırması için son derece yararlıdır:

typescript
import { get } from "@vercel/edge-config";
import { NextRequest, NextResponse } from "next/server";
 
export async function middleware(request: NextRequest) {
  // Edge Config okumaları son derece hızlıdır (~1ms) çünkü
  // veri her edge konumuna çoğaltılmıştır
  const maintenanceMode = await get<boolean>("maintenance_mode");
 
  if (maintenanceMode) {
    return NextResponse.rewrite(new URL("/maintenance", request.url));
  }
 
  // Özellik bayrakları
  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));
  }
 
  // Dinamik yönlendirmeler (yeniden dağıtım olmadan yönlendirmeleri güncelle)
  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();
}

Bu gerçek bir oyun değiştirici. Edge Config'den önce, bir özellik bayrağını değiştirmek kod değişikliği ve yeniden dağıtım demekti. Artık bir dashboard'da bir JSON değerini güncellersiniz ve saniyeler içinde küresel olarak yayılır.

Gerçek Performans Matematiği#

Pazarlama matematiği yerine dürüst matematiği yapalım. Veritabanı sorgulaması gereken tipik bir API endpoint'i için üç mimariyi karşılaştıracağım:

Senaryo: Kullanıcı Profili API'si (2 veritabanı sorgusu)#

Mimari A: Geleneksel Bölgesel Node.js

Tokyo'daki kullanıcı → Virginia'daki kaynak: 140ms
  + VT sorgusu 1 (aynı bölge): 2ms
  + VT sorgusu 2 (aynı bölge): 2ms
  + İşleme: 5ms
  = Toplam: ~149ms

Mimari B: HTTP Veritabanı ile Edge Fonksiyonu

Tokyo'daki kullanıcı → Tokyo'daki Edge: 5ms
  + VT sorgusu 1 (Virginia'ya HTTP): 145ms
  + VT sorgusu 2 (Virginia'ya HTTP): 145ms
  + İşleme: 3ms
  = Toplam: ~298ms ← Bölgeselden DAHA YAVAŞ

Mimari C: Bölgesel Veritabanı ile Edge Fonksiyonu (okuma kopyası)

Tokyo'daki kullanıcı → Tokyo'daki Edge: 5ms
  + VT sorgusu 1 (Tokyo kopyasına HTTP): 8ms
  + VT sorgusu 2 (Tokyo kopyasına HTTP): 8ms
  + İşleme: 3ms
  = Toplam: ~24ms ← En hızlı, ama çok bölgeli VT gerektirir

Mimari D: Auth İçin Edge + Veri İçin Bölgesel

Tokyo'daki kullanıcı → Tokyo'daki Edge middleware: 5ms (JWT doğrulama)
  → Virginia'daki kaynak: 140ms
  + VT sorgusu 1 (aynı bölge): 2ms
  + VT sorgusu 2 (aynı bölge): 2ms
  + İşleme: 5ms
  = Toplam: ~154ms
  (Ama auth zaten doğrulanmış — kaynağın tekrar doğrulamasına gerek yok)
  (Ve yetkisiz istekler edge'de engelleniyor — kaynağa asla ulaşmıyorlar)

Çıkarımlar:

  1. Edge + kaynak veritabanı = genellikle daha yavaş bölgesel sunucu kullanmaktan
  2. Edge + çok bölgeli veritabanı = en hızlı ama en pahalı ve karmaşık
  3. Kapı bekçiliği için edge + veri için bölgesel = en iyi pragmatik denge
  4. Saf edge (veritabanı yok) = yönlendirmeler ve auth kontrolleri gibi şeyler için rakipsiz

Mimari D, çoğu proje için kullandığım şey. Edge iyi olduğu şeyleri (hızlı kararlar, auth, yönlendirme) halleder ve bölgesel Node.js sunucusu iyi olduğu şeyleri (veritabanı sorguları, ağır hesaplama) halleder.

Edge'in Gerçekten Kazandığı Yer: Veritabanısız İşlemler#

Veritabanı dahil olmadığında matematik tamamen tersine döner:

Yönlendirme (edge):
  Tokyo'daki kullanıcı → Tokyo'daki Edge → yönlendirme yanıtı: ~5ms

Yönlendirme (bölgesel):
  Tokyo'daki kullanıcı → Virginia'daki Kaynak → yönlendirme yanıtı: ~280ms
Statik API yanıtı (edge + önbellek):
  Tokyo'daki kullanıcı → Tokyo'daki Edge → önbelleğe alınmış yanıt: ~5ms

Statik API yanıtı (bölgesel):
  Tokyo'daki kullanıcı → Virginia'daki Kaynak → yanıt: ~280ms
Bot engelleme (edge):
  Herhangi bir yerdeki kötü bot → Edge (en yakın) → 403 yanıtı: ~5ms
  (Bot kaynak sunucunuza asla ulaşmaz)

Bot engelleme (bölgesel):
  Herhangi bir yerdeki kötü bot → Virginia'daki Kaynak → 403 yanıtı: ~280ms
  (Bot yine de kaynak kaynakları tüketir)

Veritabanı gerektirmeyen işlemler için edge 20-50 kat daha hızlıdır. Bu pazarlama değil -- fizik.

Karar Çerçevem#

Edge fonksiyonlarıyla bir yıl üretimde çalıştıktan sonra, işte her yeni endpoint veya mantık parçası için kullandığım akış diyagramı:

Adım 1: Node.js API'lerine ihtiyacı var mı?#

fs, net, child_process veya herhangi bir yerel modül import ediyorsa -- Node.js bölgesel. Tartışma yok.

Adım 2: Veritabanı sorgularına ihtiyacı var mı?#

Evetse ve kullanıcılarınızın yakınında okuma kopyalarınız yoksa -- Node.js bölgesel (veritabanınızla aynı bölgede). Veritabanı gidiş-dönüşleri hakim olacaktır.

Evetse ve küresel olarak dağıtılmış okuma kopyalarınız varsa -- Edge çalışabilir, HTTP tabanlı veritabanı istemcileri kullanarak.

Adım 3: İstekle ilgili bir karar mı (yönlendirme, auth, redirect)?#

Evetse -- Edge. Tam isabet noktası. İsteğin kaynağa ulaşmadan önce ne olacağını belirleyen hızlı bir karar veriyorsunuz.

Adım 4: Yanıt önbelleğe alınabilir mi?#

Evetse -- uygun Cache-Control başlıklarıyla Edge. İlk istek kaynağınıza gitse bile, sonraki istekler edge önbelleğinden sunulur.

Adım 5: CPU yoğun mu?#

Önemli hesaplama içeriyorsa (görüntü işleme, PDF oluşturma, büyük veri dönüşümleri) -- Node.js bölgesel.

Adım 6: Gecikmeye ne kadar duyarlı?#

Arka plan işi veya webhook ise -- Node.js bölgesel. Kimse beklemiyor. Her ms'nin önemli olduğu kullanıcıya yönelik bir istekse -- diğer kriterleri karşılıyorsa Edge.

Kopya Kağıdı#

typescript
// ✅ Edge için MÜKEMMEL
// - Middleware (auth, yönlendirmeler, yeniden yazmalar, başlıklar)
// - Coğrafi konum mantığı
// - A/B test ataması
// - Bot tespiti / WAF kuralları
// - Önbellek dostu API yanıtları
// - Özellik bayrak kontrolleri
// - CORS preflight yanıtları
// - Statik veri dönüşümleri (VT yok)
// - Webhook imza doğrulaması
 
// ❌ Node.js bölgeselde TUTUN
// - Veritabanı CRUD işlemleri
// - Dosya yüklemeleri / işleme
// - Görüntü manipülasyonu
// - PDF oluşturma
// - E-posta gönderme (HTTP API kullanın, ama yine de bölgesel)
// - WebSocket sunucuları
// - Arka plan işleri / kuyruklar
// - Yerel npm paketleri kullanan her şey
// - Veritabanı sorgulu SSR sayfaları
// - Veritabanına ulaşan GraphQL çözücüler
 
// 🤔 DURUMA BAĞLI
// - Kimlik doğrulama (JWT için edge, oturum-VT için bölgesel)
// - API rotaları (VT yoksa edge, VT varsa bölgesel)
// - Sunucu render sayfaları (veri önbellek/fetch'ten geliyorsa edge, VT'den geliyorsa bölgesel)
// - Gerçek zamanlı özellikler (ilk auth için edge, kalıcı bağlantılar için bölgesel)

Edge'de Gerçekte Ne Çalıştırıyorum#

Bu site için, işte dağılım:

Edge (middleware):

  • Dil tespiti ve yönlendirme
  • Bot filtreleme
  • Güvenlik başlıkları (CSP, HSTS, vb.)
  • Erişim loglama
  • Hız sınırlama (temel)

Node.js bölgesel:

  • Blog içerik render'ı (MDX işleme Velite aracılığıyla Node.js API'leri gerektirir)
  • Redis'e dokunan API rotaları
  • OG görüntü oluşturma (daha fazla CPU süresi gerektirir)
  • RSS akışı oluşturma

Statik (hiç runtime yok):

  • Araç sayfaları (derleme zamanında önceden render edilmiş)
  • Blog yazısı sayfaları (derleme zamanında önceden render edilmiş)
  • Tüm görüntüler ve varlıklar (CDN'den sunulan)

En iyi runtime genellikle hiç runtime olmamaktır. Bir şeyi derleme zamanında önceden render edebilir ve statik varlık olarak sunabilirseniz, bu her zaman herhangi bir edge fonksiyonundan daha hızlı olacaktır. Edge, gerçekten her istekte dinamik olması gereken şeyler içindir.

Dürüst Özet#

Edge fonksiyonları geleneksel sunucuların yerini almaz. Tamamlayıcıdır. Mimari araç kutunuzdaki ek bir araçtır -- doğru kullanım alanları için son derece güçlü ve yanlış kullanım alanları için aktif olarak zararlı.

Sürekli geri döndüğüm sezgisel kural: fonksiyonunuzun tek bir bölgedeki veritabanına ulaşması gerekiyorsa, fonksiyonu edge'e koymak yardımcı olmaz -- zarar verir. Sadece bir atlama eklediniz. Fonksiyon daha hızlı çalışır, ama sonra veritabanına geri ulaşmak için 100ms+ harcar. Net sonuç: her şeyi tek bölgede çalıştırmaktan daha yavaş.

Ama yalnızca isteğin kendisindeki bilgilerle alınabilen kararlar için -- coğrafi konum, çerezler, başlıklar, JWT'ler -- edge rakipsizdir. O 5ms'lik edge yanıtları sentetik benchmark'lar değil. Gerçekler ve kullanıcılarınız farkı hissediyor.

Her şeyi edge'e taşımayın. Her şeyi edge'den uzak tutmayın. Her mantık parçasını fiziğin lehine olduğu yere koyun.

İlgili Yazılar