Giriş
Bir HTTP isteği sunucuya ulaşmadan önce araya giren ve karar veren bir katman düşünün. Kimlik doğrulama mı yapılacak? Kullanıcı doğru sayfaya mı yönlendirilecek? Belki bir güvenlik başlığı eklenmeli? İşte Next.js'te bu katmana Middleware deniyor ve açıkçası, modern web uygulamalarında bu mekanizmayı anlamadan sağlam bir proje inşa etmek oldukça zor.
Middleware, App Router mimarisinde her istek için devreye girer, Edge Runtime üzerinde çalışır ve istekleri yönlendirme, yeniden yazma, başlık ekleme veya doğrudan yanıt dönme gibi işlemleri yapmanızı sağlar.
Bu rehberde temellerden başlayıp ileri düzey kalıplara kadar ilerleyeceğiz. Kimlik doğrulama, uluslararasılaştırma, hız sınırlama, A/B testi, coğrafi konum tabanlı yönlendirme gibi gerçek dünya senaryolarını pratik kod örnekleriyle ele alacağız. Bir de 2025'te keşfedilen ve ciddi yankı uyandıran CVE-2025-29927 güvenlik açığına bakacağız — çünkü bu olay, middleware güvenliği hakkında herkesin bilmesi gereken dersler içeriyor.
Eğer daha önce Next.js Server Actions hakkındaki yazımızı okuduysanız, Middleware'in sunucu tarafı mantığını nasıl tamamladığını daha kolay kavrayacaksınız. Server Actions veri mutasyonu için harika bir çözüm sunarken, Middleware isteğin yaşam döngüsünün en başında devreye girerek güvenlik ve yönlendirme katmanı oluşturur.
Middleware Nedir ve Nasıl Çalışır?
En basit haliyle Middleware, bir istek (request) ile bir yanıt (response) arasına oturan bir kod parçasıdır. Next.js'te sayfa veya API route'u render edilmeden önce çalışır. Yani her gelen isteği inceleyebilir, değiştirebilir veya tamamen engelleyebilirsiniz.
Edge Runtime Üzerinde Çalışma
Next.js Middleware'in en dikkat çekici yanı Edge Runtime üzerinde çalışmasıdır. Edge Runtime, geleneksel Node.js runtime'ından çok daha hafif ve hızlı bir ortam. Vercel gibi platformlarda kullanıcıya en yakın veri merkezinde çalışır, böylece gecikme süresi minimuma iner.
Edge Runtime'ın avantajları:
- Düşük gecikme: Kullanıcıya coğrafi olarak yakın çalışır
- Hızlı soğuk başlatma: Milisaniyeler içinde ayağa kalkar
- Küçük boyut: Minimum bellek tüketir
- Web Standartları API'leri: Fetch API, Web Crypto API gibi standart API'leri kullanır
Ama tabii bu avantajların bir bedeli var. Edge Runtime, Node.js'in tüm API'lerini desteklemiyor. Mesela fs (dosya sistemi), child_process veya bazı crypto fonksiyonları Edge Runtime'da kullanılamaz. Bu sınırlamalara ileride daha detaylı değineceğiz.
Çalışma Akışı
Bir istek geldiğinde Next.js'teki akış kabaca şöyle işler:
- İstek sunucuya ulaşır
- Middleware çalışır (matcher eşleşiyorsa)
- Middleware karar verir: devam et, yönlendir, yeniden yaz veya doğrudan yanıt dön
- Devam edilirse ilgili sayfa veya API route'u render edilir
- Yanıt istemciye döner
İlk Middleware'inizi Oluşturma
Next.js'te middleware oluşturmak aslında oldukça basit. Projenizin kök dizininde (veya src/ klasörü kullanıyorsanız src/ içinde) bir middleware.ts dosyası oluşturmanız yeterli.
Önemli not: Next.js 16 ile birlikte middleware.ts dosya adı proxy.ts olarak değişti. Next.js 15 ve öncesinde middleware.ts, Next.js 16 ve sonrasında ise proxy.ts kullanmanız gerekiyor. Bu makaledeki örnekler her iki sürüm için de geçerli — sadece dosya adını uygun şekilde değiştirmeniz yeterli.
// middleware.ts (Next.js 15) veya proxy.ts (Next.js 16)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// Middleware fonksiyonu: her eşleşen istek için çalışır
export function middleware(request: NextRequest) {
// İsteğin URL'ini konsola yazdır
console.log('İstek geldi:', request.nextUrl.pathname);
// İsteğe özel bir başlık ekleyerek devam et
const response = NextResponse.next();
response.headers.set('x-middleware-cache', 'no-cache');
return response;
}
// Hangi yolların middleware'den geçeceğini belirle
export const config = {
matcher: [
// Tüm sayfa isteklerini eşleştir, statik dosyaları hariç tut
'/((?!_next/static|_next/image|favicon.ico).*)',
],
};
Bu basit örnekte üç temel kavram var:
- NextRequest: Gelen istek hakkında bilgi içerir (URL, başlıklar, çerezler, coğrafi konum vb.)
- NextResponse: Yanıt oluşturmak veya isteği değiştirmek için kullanılır
- config.matcher: Middleware'in hangi yollar için çalışacağını belirler
Temel Yanıt Türleri
Middleware'den dönebileceğiniz temel yanıt türlerini bir görelim:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 1. İsteği olduğu gibi devam ettir
if (pathname === '/herkese-acik') {
return NextResponse.next();
}
// 2. Başka bir sayfaya yönlendir (redirect)
if (pathname === '/eski-sayfa') {
return NextResponse.redirect(new URL('/yeni-sayfa', request.url));
}
// 3. İsteği farklı bir sayfaya yeniden yaz (rewrite)
// URL değişmez ama farklı içerik sunulur
if (pathname === '/deneysel') {
return NextResponse.rewrite(new URL('/deneysel-v2', request.url));
}
// 4. Doğrudan JSON yanıt dön
if (pathname === '/api/durum') {
return NextResponse.json(
{ durum: 'aktif', zaman: new Date().toISOString() },
{ status: 200 }
);
}
return NextResponse.next();
}
Matcher Yapılandırması
Matcher yapılandırması, middleware'inizin hangi istekler için çalışacağını belirler. Doğru yapılandırılmış bir matcher gereksiz middleware çağrılarını engeller ve uygulamanızın performansını doğrudan etkiler. Yani bu kısmı atlamayın.
Temel Matcher Desenleri
// Basit yol eşleştirme
export const config = {
matcher: '/hakkimizda',
};
// Birden fazla yol eşleştirme
export const config = {
matcher: ['/hakkimizda', '/iletisim', '/blog'],
};
// Joker karakter kullanarak eşleştirme
export const config = {
matcher: '/blog/:path*', // /blog, /blog/yazi-1, /blog/kategori/yazi-2 vb.
};
Regex Desenleri ve Negatif Öngörüş (Negative Lookahead)
Daha karmaşık eşleştirme gereksinimleri için regex desenleri kullanabilirsiniz. En yaygın kullanım, statik dosyaları ve belirli yolları hariç tutmak:
export const config = {
matcher: [
// Statik dosyaları ve API rotalarını hariç tutan genel desen
// Negatif öngörüş (negative lookahead) kullanır
'/((?!api|_next/static|_next/image|favicon\\.ico|robots\\.txt|sitemap\\.xml).*)',
],
};
Bu desen şöyle çalışıyor:
(?!...)— Negatif öngörüş: belirtilen desenlerin başlamadığı yolları eşleştirirapi— API rotalarını hariç tutar_next/static— Statik varlıkları (JS, CSS dosyaları) hariç tutar_next/image— Optimize edilmiş görselleri hariç tutarfavicon\\.ico— Favicon dosyasını hariç tutar
Koşullu Eşleştirme
Matcher dışında, middleware fonksiyonunun içinde de koşullu mantık kurabilirsiniz:
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Yalnızca /panel altındaki rotalar için kimlik doğrulama kontrolü
if (pathname.startsWith('/panel')) {
const token = request.cookies.get('oturum-token');
if (!token) {
return NextResponse.redirect(new URL('/giris', request.url));
}
}
// Yalnızca /api rotaları için hız sınırlama
if (pathname.startsWith('/api')) {
// Hız sınırlama mantığı
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon\\.ico).*)'],
};
Kimlik Doğrulama ve Yetkilendirme
Middleware'in en yaygın kullanım alanlarından biri kimlik doğrulama ve yetkilendirme kontrolleri. Kullanıcının oturum durumunu kontrol ederek korumalı sayfalara erişimi yönetebilirsiniz. Hadi birkaç farklı yaklaşıma bakalım.
JWT Token Doğrulama
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';
// JWT gizli anahtarını çevre değişkeninden al
const JWT_SECRET = new TextEncoder().encode(
process.env.JWT_SECRET || 'gizli-anahtar'
);
// Korumalı rotaların listesi
const KORUMALI_ROTALAR = ['/panel', '/profil', '/ayarlar'];
// Yalnızca yönetici erişimi gerektiren rotalar
const YONETICI_ROTALARI = ['/panel/yonetim', '/panel/kullanicilar'];
async function tokenDogrula(token: string) {
try {
// jose kütüphanesi Edge Runtime ile uyumludur
const { payload } = await jwtVerify(token, JWT_SECRET);
return payload;
} catch (hata) {
return null;
}
}
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Korumalı rota kontrolü
const korunmaliRota = KORUMALI_ROTALAR.some((rota) =>
pathname.startsWith(rota)
);
if (!korunmaliRota) {
return NextResponse.next();
}
// Çerezden veya Authorization başlığından token al
const token =
request.cookies.get('auth-token')?.value ||
request.headers.get('authorization')?.replace('Bearer ', '');
if (!token) {
// Giriş sayfasına yönlendir, geri dönüş URL'ini kaydet
const girisUrl = new URL('/giris', request.url);
girisUrl.searchParams.set('geriDonus', pathname);
return NextResponse.redirect(girisUrl);
}
// Token doğrulama
const payload = await tokenDogrula(token);
if (!payload) {
// Geçersiz token: çerezi temizle ve girişe yönlendir
const yanit = NextResponse.redirect(new URL('/giris', request.url));
yanit.cookies.delete('auth-token');
return yanit;
}
// Yönetici rota kontrolü
const yoneticiRotasi = YONETICI_ROTALARI.some((rota) =>
pathname.startsWith(rota)
);
if (yoneticiRotasi && payload.rol !== 'yonetici') {
// Yetkisiz erişim: 403 sayfasına yönlendir
return NextResponse.redirect(new URL('/yetkisiz', request.url));
}
// Kullanıcı bilgisini başlıklara ekle (downstream kullanım için)
const yanit = NextResponse.next();
yanit.headers.set('x-kullanici-id', payload.sub as string);
yanit.headers.set('x-kullanici-rol', payload.rol as string);
return yanit;
}
Oturum Çerezi ile Doğrulama
JWT yerine basit bir oturum çerezi yaklaşımı da kullanabilirsiniz:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const oturumCerezi = request.cookies.get('oturum-id');
const { pathname } = request.nextUrl;
// Giriş sayfasındaki kullanıcı zaten oturum açmışsa panele yönlendir
if (pathname === '/giris' && oturumCerezi) {
return NextResponse.redirect(new URL('/panel', request.url));
}
// Korumalı sayfalarda oturum kontrolü
if (pathname.startsWith('/panel') && !oturumCerezi) {
const girisUrl = new URL('/giris', request.url);
girisUrl.searchParams.set('geriDonus', pathname);
return NextResponse.redirect(girisUrl);
}
return NextResponse.next();
}
Önemli uyarı: Middleware'deki kimlik doğrulama yalnızca bir ilk savunma hattıdır. Birazdan göreceğiniz gibi, sadece middleware'e güvenmek ciddi güvenlik açıklarına yol açabiliyor. Her zaman veri erişim katmanında da doğrulama yapın.
CVE-2025-29927 Güvenlik Açığı ve Dersler
Mart 2025'te keşfedilen CVE-2025-29927, Next.js middleware güvenliği konusunda adeta bir deprem etkisi yarattı. Bu güvenlik açığı, saldırganların middleware kontrollerini tamamen atlayabilmesine olanak tanıyordu. Evet, tamamen.
Açığın Teknik Detayları
Next.js, dahili olarak x-middleware-subrequest adında bir HTTP başlığı kullanıyordu. Bu başlık, middleware'in kendi kendini çağırmasından kaynaklanan sonsuz döngüleri önlemek için tasarlanmıştı. Başlık mevcut olduğunda Next.js, middleware'i atlayıp isteği doğrudan hedef rotaya yönlendiriyordu.
Sorun ne miydi? Bu başlık dışarıdan gelen isteklerde filtrelenmiyordu. Yani bir saldırgan, isteğine basitçe bu başlığı ekleyerek tüm middleware kontrollerini — kimlik doğrulama, yetkilendirme, CSP başlıkları, hız sınırlama dahil — bypass edebiliyordu. Düşünün, tek bir HTTP başlığı ile tüm güvenlik duvarınız çöküyor.
# Saldırı örneği (ETKİLENEN SÜRÜMLERDE)
# Bu başlığı ekleyerek middleware tamamen atlanabiliyordu
curl -H "x-middleware-subrequest: middleware" https://hedef-site.com/panel/gizli-sayfa
Etkilenen Sürümler
- Next.js 12.x: 12.3.5 öncesi tüm sürümler
- Next.js 13.x: 13.5.9 öncesi tüm sürümler
- Next.js 14.x: 14.2.25 öncesi tüm sürümler
Azaltma Stratejileri
- Hemen güncelleme yapın: Next.js'i yamanın içeren en son sürüme yükseltin
- Başlığı engelleyin: Eğer hemen güncelleyemiyorsanız, ters proxy (Nginx, Cloudflare vb.) seviyesinde
x-middleware-subrequestbaşlığını dış isteklerden temizleyin - Çok katmanlı doğrulama uygulayın: Middleware'e asla tek güvenlik katmanı olarak güvenmeyin
# Nginx ile x-middleware-subrequest başlığını temizleme
# nginx.conf içinde
location / {
# Dışarıdan gelen isteklerdeki tehlikeli başlığı kaldır
proxy_set_header x-middleware-subrequest "";
proxy_pass http://nextjs-uygulama:3000;
}
Çıkarılan Dersler
Bu güvenlik açığından çıkarılması gereken en kritik ders aslında çok net: Middleware'i asla tek güvenlik katmanı olarak kullanmayın. Middleware, güvenlik mimarinizin sadece ilk katmanı olmalı. Gerçek kimlik doğrulama ve yetkilendirme kontrolleri, mutlaka veri erişim katmanında da yapılmalı.
// ❌ YANLIŞ: Yalnızca middleware'e güvenme
// middleware.ts
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token');
if (!token) return NextResponse.redirect('/giris');
return NextResponse.next(); // Token varsa geçir, başka doğrulama yok
}
// ✔ DOĞRU: Veri erişim katmanında da doğrulama
// lib/veri-erisim.ts
export async function kullaniciBilgisiGetir(kullaniciId: string) {
// Oturumu bağımsız olarak doğrula
const oturum = await oturumuDogrula();
if (!oturum || oturum.kullaniciId !== kullaniciId) {
throw new Error('Yetkisiz erişim');
}
// Ancak doğrulama başarılıysa veritabanına eriş
return await db.kullanicilar.findUnique({
where: { id: kullaniciId },
});
}
Bu yaklaşım, güvenlik dünyasında "derinlemesine savunma" (defense in depth) olarak bilinen bir prensip. Bir katman aşılsa bile diğer katmanlar korumayı sürdürür. Açıkçası, CVE-2025-29927 bu prensibin neden bu kadar önemli olduğunun en somut kanıtlarından biri.
Uluslararasılaştırma (i18n) Yönlendirmesi
Çok dilli uygulamalarda middleware, kullanıcıyı tercih ettiği dile yönlendirmek için biçilmiş kaftan. Accept-Language başlığını okuyarak, çerezleri kontrol ederek veya URL yapısına göre doğru dil sürümünü sunabilirsiniz.
Temel Dil Algılama ve Yönlendirme
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// Desteklenen diller
const DESTEKLENEN_DILLER = ['tr', 'en', 'de', 'fr'];
const VARSAYILAN_DIL = 'tr';
// Accept-Language başlığını ayrıştırarak tercih edilen dili bul
function tercihEdilenDiliBul(acceptLanguage: string | null): string {
if (!acceptLanguage) return VARSAYILAN_DIL;
// Accept-Language başlığını ayrıştır (örn: "tr-TR,tr;q=0.9,en;q=0.8")
const diller = acceptLanguage
.split(',')
.map((dil) => {
const [kod, kalite] = dil.trim().split(';q=');
return {
kod: kod.split('-')[0].toLowerCase(), // "tr-TR" -> "tr"
kalite: kalite ? parseFloat(kalite) : 1.0,
};
})
.sort((a, b) => b.kalite - a.kalite);
// Desteklenen dillerle eşleştir
for (const dil of diller) {
if (DESTEKLENEN_DILLER.includes(dil.kod)) {
return dil.kod;
}
}
return VARSAYILAN_DIL;
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Zaten bir dil öneki varsa devam et
const mevcutDil = DESTEKLENEN_DILLER.find(
(dil) => pathname.startsWith(`/${dil}/`) || pathname === `/${dil}`
);
if (mevcutDil) {
return NextResponse.next();
}
// Statik dosyaları atla
if (
pathname.startsWith('/_next') ||
pathname.includes('.') // dosya uzantısı olan istekler
) {
return NextResponse.next();
}
// Çerezden dil tercihini kontrol et
const cerezDili = request.cookies.get('tercih-edilen-dil')?.value;
// Dili belirle: önce çerez, sonra Accept-Language başlığı
const dil =
cerezDili && DESTEKLENEN_DILLER.includes(cerezDili)
? cerezDili
: tercihEdilenDiliBul(request.headers.get('accept-language'));
// Kullanıcıyı doğru dil yoluna yönlendir
const yeniUrl = new URL(`/${dil}${pathname}`, request.url);
yeniUrl.search = request.nextUrl.search; // Sorgu parametrelerini koru
return NextResponse.redirect(yeniUrl);
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon\\.ico).*)'],
};
next-intl ile Entegrasyon
Daha kapsamlı bir i18n çözümü arıyorsanız, next-intl kütüphanesi yerleşik middleware desteğiyle işinizi çok kolaylaştırır:
// middleware.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
// next-intl middleware'ini oluştur
export default createMiddleware(routing);
export const config = {
// next-intl'in önerdiği matcher deseni
matcher: ['/', '/(tr|en|de|fr)/:path*'],
};
// i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
// Desteklenen diller
locales: ['tr', 'en', 'de', 'fr'],
// Varsayılan dil
defaultLocale: 'tr',
// Varsayılan dil için URL önekini gizle
// /tr/hakkimizda yerine /hakkimizda gösterilir
localePrefix: 'as-needed',
});
Bu yapılandırmayla App Router'daki klasör yapınız app/[locale]/ şeklinde olmalı. Her sayfa otomatik olarak locale parametresini alır ve ilgili çeviri dosyasını kullanır.
Hız Sınırlama (Rate Limiting)
API'lerinizi kötüye kullanımdan korumak istiyorsanız, middleware seviyesinde hız sınırlama uygulamak mantıklı bir seçenek. Edge Runtime'da çalıştığı için istekler en erken aşamada filtreleniyor — bu da sunucu kaynaklarınızı gereksiz yükten korur.
Upstash Redis ile Kayan Pencere Algoritması
Edge Runtime'da kullanabileceğiniz en popüler çözüm Upstash Redis. Neden mi? Upstash, HTTP tabanlı bir Redis hizmeti sunduğu için Edge Runtime ile sorunsuz çalışıyor (TCP bağlantısı gerektirmiyor, ki bu Edge'de zaten mümkün değil).
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
// Upstash Redis istemcisini oluştur
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
// Kayan pencere (sliding window) algoritmasıyla hız sınırlayıcı
const hizSinirla = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(
20, // Maksimum istek sayısı
'60 s' // Zaman penceresi: 60 saniye
),
analytics: true, // Upstash Dashboard'da analitik görüntüle
prefix: 'uygulama:hiz-sinir', // Redis anahtar öneki
});
// IP adresini güvenli bir şekilde al
function ipAdresiAl(request: NextRequest): string {
const xForwardedFor = request.headers.get('x-forwarded-for');
if (xForwardedFor) {
return xForwardedFor.split(',')[0].trim();
}
return request.headers.get('x-real-ip') || '127.0.0.1';
}
export async function middleware(request: NextRequest) {
// Yalnızca API rotaları için hız sınırlama uygula
if (!request.nextUrl.pathname.startsWith('/api')) {
return NextResponse.next();
}
const ip = ipAdresiAl(request);
const { success, limit, remaining, reset } = await hizSinirla.limit(ip);
// Hız sınırı bilgilerini yanıt başlıklarına ekle
const yanit = success
? NextResponse.next()
: NextResponse.json(
{
hata: 'Hız sınırı aşıldı',
mesaj: 'Lütfen daha sonra tekrar deneyin.',
},
{ status: 429 }
);
yanit.headers.set('X-RateLimit-Limit', limit.toString());
yanit.headers.set('X-RateLimit-Remaining', remaining.toString());
yanit.headers.set('X-RateLimit-Reset', reset.toString());
return yanit;
}
export const config = {
matcher: '/api/:path*',
};
Rota Bazında Farklı Sınırlar
Her API endpoint'inin aynı hız sınırına ihtiyacı yok. Örneğin giriş endpoint'ine brute force koruması için çok daha sıkı bir limit koyabilirsiniz:
// Farklı rotalar için farklı hız sınırlayıcılar
const sinirlar = {
// Genel API: dakikada 60 istek
genel: new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(60, '60 s'),
prefix: 'hs:genel',
}),
// Kimlik doğrulama: dakikada 5 istek (brute force korunması)
giris: new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(5, '60 s'),
prefix: 'hs:giris',
}),
// Dosya yükleme: saatte 10 istek
yukleme: new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '3600 s'),
prefix: 'hs:yukleme',
}),
};
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const ip = ipAdresiAl(request);
// Rotaya göre uygun sınırlayıcıyı seç
let sinir;
if (pathname.startsWith('/api/giris') || pathname.startsWith('/api/kayit')) {
sinir = sinirlar.giris;
} else if (pathname.startsWith('/api/yukleme')) {
sinir = sinirlar.yukleme;
} else {
sinir = sinirlar.genel;
}
const sonuc = await sinir.limit(ip);
if (!sonuc.success) {
return NextResponse.json(
{ hata: 'Hız sınırı aşıldı' },
{ status: 429 }
);
}
return NextResponse.next();
}
A/B Testi ve Özellik Bayrakları
Middleware, A/B testi ve özellik bayrakları (feature flags) için gerçekten ideal bir katman. Çerez tabanlı kullanıcı segmentasyonu ve rewrite mekanizmasıyla, kullanıcılar URL'de hiçbir fark görmeden farklı sayfa varyasyonlarıyla karşılaşabilir.
Çerez Tabanlı A/B Testi
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// A/B test grupları
const TEST_GRUPLARI = ['kontrol', 'deney-a', 'deney-b'] as const;
type TestGrubu = (typeof TEST_GRUPLARI)[number];
// Kullanıcıyı rastgele bir gruba ata
function rastgeleGrupAta(): TestGrubu {
const rastgele = Math.random();
if (rastgele < 0.33) return 'kontrol';
if (rastgele < 0.66) return 'deney-a';
return 'deney-b';
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Yalnızca A/B test uygulanan sayfalarda çalış
if (pathname !== '/fiyatlandirma') {
return NextResponse.next();
}
// Mevcut grup atasını çerezden kontrol et
let grup = request.cookies.get('ab-fiyat-testi')?.value as
| TestGrubu
| undefined;
// Eğer çerez yoksa yeni grup ata
if (!grup || !TEST_GRUPLARI.includes(grup)) {
grup = rastgeleGrupAta();
}
// Gruba göre farklı sayfaya yeniden yaz (rewrite)
// URL değişmez, kullanıcı hala /fiyatlandirma görür
const yanit = NextResponse.rewrite(
new URL(`/fiyatlandirma/${grup}`, request.url)
);
// Grup bilgisini çereze kaydet (30 gün geçerli)
yanit.cookies.set('ab-fiyat-testi', grup, {
maxAge: 60 * 60 * 24 * 30,
httpOnly: true,
sameSite: 'lax',
path: '/',
});
return yanit;
}
Özellik Bayrakları ile Koşullu Yönlendirme
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// Özellik bayrakları yapılandırması
const OZELLIK_BAYRAKLARI: Record<string, {
aktif: boolean;
yuzdesi: number; // Kullanıcıların yüzde kaçı görecek
}> = {
'yeni-arayuz': { aktif: true, yuzdesi: 50 },
'gelismis-arama': { aktif: true, yuzdesi: 25 },
'karanlik-mod': { aktif: false, yuzdesi: 0 },
};
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const yanit = NextResponse.next();
// Her özellik bayrağını kontrol et
for (const [bayrak, ayar] of Object.entries(OZELLIK_BAYRAKLARI)) {
if (!ayar.aktif) continue;
const cerezAdi = `ff-${bayrak}`;
let deger = request.cookies.get(cerezAdi)?.value;
// İlk ziyarette bayrağı ata
if (!deger) {
deger = Math.random() * 100 < ayar.yuzdesi ? 'acik' : 'kapali';
yanit.cookies.set(cerezAdi, deger, {
maxAge: 60 * 60 * 24 * 7,
path: '/',
});
}
// Bayrak değerini başlığa ekle (sayfa bileşenlerinde kullanmak için)
yanit.headers.set(`x-ff-${bayrak}`, deger);
}
// Özellik bayrağına göre yeniden yazma örneği
if (
pathname === '/arama' &&
request.cookies.get('ff-gelismis-arama')?.value === 'acik'
) {
return NextResponse.rewrite(new URL('/arama/v2', request.url));
}
return yanit;
}
Bu yapıyla app/fiyatlandirma/kontrol/page.tsx, app/fiyatlandirma/deney-a/page.tsx ve app/fiyatlandirma/deney-b/page.tsx dosyalarını oluşturarak her gruba farklı bir deneyim sunabilirsiniz. Kullanıcı URL'de hiçbir değişiklik görmez — her zaman /fiyatlandirma adresini görür. Oldukça zarif bir çözüm, değil mi?
Coğrafi Konum Tabanlı Yönlendirme
Next.js middleware'i, gelen istekle birlikte coğrafi konum bilgisi sağlar. Bu bilgiyi kullanarak kullanıcıları ülke veya bölgelerine göre farklı içeriklere yönlendirebilirsiniz.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// Ülke koduna göre dil eşlemesi
const ULKE_DIL_ESLEME: Record<string, string> = {
TR: 'tr',
DE: 'de',
AT: 'de',
CH: 'de',
US: 'en',
GB: 'en',
FR: 'fr',
};
// Bölgesel fiyatlandırma bölgeleri
const FIYAT_BOLGELERI: Record<string, string> = {
TR: 'turkiye',
US: 'amerika',
GB: 'avrupa',
DE: 'avrupa',
FR: 'avrupa',
JP: 'asya',
};
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Coğrafi konum bilgisini al
// Not: Bu bilgi Vercel gibi platformlarda otomatik doldurulur
// Yerel geliştirmede boş olabilir
const ulke = request.geo?.country || 'TR';
const sehir = request.geo?.city || 'bilinmiyor';
// Coğrafi konum bilgilerini başlıklara ekle
const yanit = NextResponse.next();
yanit.headers.set('x-kullanici-ulke', ulke);
yanit.headers.set('x-kullanici-sehir', sehir);
// Fiyatlandırma sayfası için bölgesel yönlendirme
if (pathname === '/fiyatlandirma') {
const fiyatBolgesi = FIYAT_BOLGELERI[ulke] || 'varsayilan';
return NextResponse.rewrite(
new URL(`/fiyatlandirma/${fiyatBolgesi}`, request.url)
);
}
// Ülkeye göre otomatik dil yönlendirmesi
if (pathname === '/') {
const tercihEdilenDil = ULKE_DIL_ESLEME[ulke] || 'en';
const mevcutDilTercihi = request.cookies.get('dil-tercihi')?.value;
// Kullanıcının manuel seçimi varsa onu kullan
if (!mevcutDilTercihi) {
return NextResponse.redirect(
new URL(`/${tercihEdilenDil}`, request.url)
);
}
}
return yanit;
}
Dikkat: request.geo özelliği yalnızca Vercel gibi bunu destekleyen platformlarda çalışır. Kendi sunucunuzda çalıştırıyorsanız, coğrafi konum bilgisini bir IP geolocation servisi (mesela MaxMind GeoIP) aracılığıyla kendiniz eklemeniz gerekecektir.
Edge Runtime Sınırlamaları ve En İyi Uygulamalar
Edge Runtime performans açısından harika, ama bazı önemli sınırlamalarını bilmeden yola çıkarsanız saatlerce hata ayıklamakla uğraşabilirsiniz. Şimdi bunlara bir göz atalım.
Kullanılamayan API'ler ve Modüller
- Dosya sistemi (
fs): Dosya okuma/yazma işlemleri yapılamaz - Node.js yerleşik modülleri:
path,os,child_process,net,tlsgibi modüller kullanılamaz - Node.js
cryptomodülü: Bunun yerine Web Crypto API (crypto.subtle) kullanılmalı - Yerel (native) modüller: C/C++ ile yazılmış yerel eklentiler çalışmaz
- TCP/UDP soketleri: Doğrudan soket bağlantıları kurulamaz (bu yüzden geleneksel Redis istemcileri çalışmaz; Upstash gibi HTTP tabanlı alternatifler şart)
Boyut Sınırlamaları
Middleware kodu ve bağımlılıkları, sıkıştırılmamış haliyle genellikle 1 MB sınırına tabi (barındırma platformuna göre değişebilir). Bu yüzden büyük kütüphaneleri middleware'e dahil etmekten kaçının.
En İyi Uygulamalar
// ❌ YANLIŞ: Ağır işlemleri middleware'de yapma
export async function middleware(request: NextRequest) {
// Veritabanından tüm kullanıcıları çekme - ÇOK YAVAŞ!
const kullanicilar = await fetch('https://api.ornek.com/tum-kullanicilar');
const veri = await kullanicilar.json();
// Karmaşık veri işleme - EDGE RUNTIME İÇİN ÇOK AĞIR!
const islenmisVeri = veri.map((k: any) => /* karmaşık hesaplama */ k);
return NextResponse.next();
}
// ✔ DOĞRU: Middleware'i hafif tutun
export async function middleware(request: NextRequest) {
// Yalnızca basit bir token kontrolü
const token = request.cookies.get('auth-token')?.value;
if (!token) {
return NextResponse.redirect(new URL('/giris', request.url));
}
// Hafif bir doğrulama (tam doğrulama sayfa seviyesinde yapılsın)
try {
const { payload } = await jwtVerify(token, secret);
const yanit = NextResponse.next();
yanit.headers.set('x-kullanici-id', payload.sub as string);
return yanit;
} catch {
return NextResponse.redirect(new URL('/giris', request.url));
}
}
Middleware'i hafif tutmak için şu prensiplere dikkat edin:
- Minimum dış çağrı: Mümkünse dış API çağrılarından kaçının. Zorunluysa tek bir hızlı çağrı yapın.
- Küçük bağımlılıklar: Yalnızca Edge Runtime ile uyumlu, küçük kütüphaneler kullanın (
jose,@upstash/redisgibi). - Erken çıkış: Eşleşmeyen istekler için mümkün olan en erken noktada
NextResponse.next()dönün. - Önbellekleme: Mümkün olan her yerde önbellekleme mekanizmaları kullanın.
Güvenlik En İyi Uygulamaları
CVE-2025-29927 deneyiminden sonra middleware güvenliği konusunu ciddiye almamak elde değil. Gelin, alınması gereken önlemleri kapsamlı bir şekilde ele alalım.
Derinlemesine Savunma (Defense in Depth)
Güvenli bir uygulama oluşturmanın altın kuralı: tek bir katmana güvenmeyin. Birden fazla savunma katmanı oluşturun ki biri aşılsa diğeri devreye girsin.
// 1. KATMAN: Middleware - İlk savunma hattı
// middleware.ts
export async function middleware(request: NextRequest) {
// Hızlı token varlık kontrolü
const token = request.cookies.get('auth-token')?.value;
if (!token && request.nextUrl.pathname.startsWith('/panel')) {
return NextResponse.redirect(new URL('/giris', request.url));
}
return NextResponse.next();
}
// 2. KATMAN: Sayfa seviyesi - Sunucu bileşeninde doğrulama
// app/panel/page.tsx
import { redirect } from 'next/navigation';
import { oturumuDogrula } from '@/lib/auth';
export default async function PanelSayfasi() {
const oturum = await oturumuDogrula();
if (!oturum) {
redirect('/giris');
}
return <div>Merhaba, {oturum.kullaniciAdi}</div>;
}
// 3. KATMAN: Veri erişim katmanı - En kritik savunma
// lib/veri-erisim.ts
import { oturumuDogrula } from '@/lib/auth';
export async function panelVerileriniGetir() {
const oturum = await oturumuDogrula();
if (!oturum) {
throw new Error('Yetkisiz erişim');
}
return await db.panelVerileri.findMany({
where: { kullaniciId: oturum.kullaniciId },
});
}
Güvenlik Başlıkları Ekleme
Middleware, güvenlik başlıklarını eklemek ve potansiyel olarak tehlikeli başlıkları temizlemek için birebir:
export function middleware(request: NextRequest) {
const yanit = NextResponse.next();
// Güvenlik başlıkları ekle
yanit.headers.set('X-Content-Type-Options', 'nosniff');
yanit.headers.set('X-Frame-Options', 'DENY');
yanit.headers.set('X-XSS-Protection', '1; mode=block');
yanit.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
yanit.headers.set(
'Strict-Transport-Security',
'max-age=63072000; includeSubDomains; preload'
);
// Content Security Policy
yanit.headers.set(
'Content-Security-Policy',
[
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' blob: data:",
"font-src 'self'",
"connect-src 'self'",
"frame-ancestors 'none'",
].join('; ')
);
// Dahili başlıkların dışarı sızmamasını sağla
yanit.headers.delete('x-powered-by');
yanit.headers.delete('server');
return yanit;
}
CORS Yönetimi
// İzin verilen kaynaklar
const IZIN_VERILEN_KAYNAKLAR = [
'https://www.ornek.com',
'https://uygulama.ornek.com',
];
export function middleware(request: NextRequest) {
// Yalnızca API rotaları için CORS uygula
if (!request.nextUrl.pathname.startsWith('/api')) {
return NextResponse.next();
}
const kaynak = request.headers.get('origin') || '';
const izinliMi = IZIN_VERILEN_KAYNAKLAR.includes(kaynak);
// Preflight (OPTIONS) isteklerini işle
if (request.method === 'OPTIONS') {
return new NextResponse(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': izinliMi ? kaynak : '',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
},
});
}
// Normal istekler için CORS başlıkları ekle
const yanit = NextResponse.next();
if (izinliMi) {
yanit.headers.set('Access-Control-Allow-Origin', kaynak);
yanit.headers.set('Access-Control-Allow-Credentials', 'true');
}
return yanit;
}
Kapsamlı Middleware Örneği
Şimdi tüm bu güvenlik uygulamalarını bir araya getiren kapsamlı bir örneğe bakalım. Bu, gerçek bir projede kullanabileceğiniz türden bir middleware:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);
// Korumalı ve genel rotaları tanımla
const GENEL_ROTALAR = ['/giris', '/kayit', '/sifremi-unuttum', '/hakkimizda'];
const YONETICI_ROTALARI = ['/panel/yonetim'];
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const yanit = NextResponse.next();
// 1. Güvenlik başlıklarını her yanıta ekle
yanit.headers.set('X-Content-Type-Options', 'nosniff');
yanit.headers.set('X-Frame-Options', 'DENY');
// 2. Genel rotalarda kimlik doğrulama gerekmez
if (GENEL_ROTALAR.some((r) => pathname.startsWith(r))) {
return yanit;
}
// 3. API rotaları için ek kontroller
if (pathname.startsWith('/api')) {
return yanit;
}
// 4. Korumalı rotalar için kimlik doğrulama
if (pathname.startsWith('/panel') || pathname.startsWith('/profil')) {
const token = request.cookies.get('auth-token')?.value;
if (!token) {
const girisUrl = new URL('/giris', request.url);
girisUrl.searchParams.set('geriDonus', pathname);
return NextResponse.redirect(girisUrl);
}
try {
const { payload } = await jwtVerify(token, JWT_SECRET);
// Yönetici rotaları için ek yetkilendirme
if (
YONETICI_ROTALARI.some((r) => pathname.startsWith(r)) &&
payload.rol !== 'yonetici'
) {
return NextResponse.redirect(new URL('/yetkisiz', request.url));
}
// Kullanıcı bilgilerini başlıklara ekle
yanit.headers.set('x-kullanici-id', payload.sub as string);
yanit.headers.set('x-kullanici-rol', payload.rol as string);
} catch {
// Geçersiz token
const temizYanit = NextResponse.redirect(new URL('/giris', request.url));
temizYanit.cookies.delete('auth-token');
return temizYanit;
}
}
return yanit;
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon\\.ico|robots\\.txt|sitemap\\.xml).*)',
],
};
Sonuç
Next.js Middleware, modern web uygulamalarında güvenlik, performans ve kullanıcı deneyimi açısından gerçekten vazgeçilmez bir araç. Edge Runtime sayesinde düşük gecikme ile hızlı karar verme imkanı sunuyor ve uygulamanızın ilk savunma hattını oluşturuyor.
Bu rehberde neler ele aldık, kısaca özetleyelim:
- Temel Kavramlar: Middleware'in ne olduğu, Edge Runtime üzerinde nasıl çalıştığı ve NextRequest/NextResponse API'leri
- Matcher Yapılandırması: Regex desenleri, negatif öngörüş ve statik dosyaları hariç tutma yöntemleri
- Kimlik Doğrulama: JWT token doğrulama, oturum yönetimi ve korumalı rota kalıpları
- CVE-2025-29927: x-middleware-subrequest başlık atlama açığı ve neden sadece middleware'e güvenmemeniz gerektiği
- Uluslararasılaştırma: Accept-Language başlığı ayrıştırma, çerez tabanlı dil tercihi ve next-intl entegrasyonu
- Hız Sınırlama: Upstash Redis ile kayan pencere algoritması ve rota bazında farklı limitler
- A/B Testi: Çerez tabanlı kullanıcı segmentasyonu ve özellik bayrakları
- Coğrafi Konum: Ülke tabanlı yönlendirme ve bölgesel içerik sunma
- Güvenlik: Derinlemesine savunma prensibi, güvenlik başlıkları ve CORS yönetimi
Son olarak birkaç tavsiye:
- Middleware'i hafif tutun. Ağır hesaplamalar ve veritabanı sorguları sunucu bileşenlerinde veya API rotalarında yapılmalı.
- Derinlemesine savunma uygulayın. Middleware yalnızca ilk filtre olmalı; gerçek yetkilendirme kontrolleri veri erişim katmanında da tekrarlanmalı.
- Next.js sürümünüzü güncel tutun. CVE-2025-29927 gibi güvenlik açıkları ancak güncelleme ile tamamen giderilebilir.
- Edge Runtime sınırlamalarını bilin. Kullandığınız kütüphanelerin Edge uyumlu olduğundan emin olun.
- Next.js 16'ya geçişte dosya adını güncelleyin.
middleware.tsyerineproxy.tskullanmanız gerekecek.
Middleware, doğru kullanıldığında Next.js uygulamalarınızın güvenliğini, performansını ve kullanıcı deneyimini ciddi anlamda iyileştirebilir. Ama her güçlü araç gibi, sorumlulukla ve en iyi uygulamalar doğrultusunda kullanılmalı. Derinlemesine savunma prensibini asla ihmal etmeyin — güvenliği tek bir katmana bırakmak yerine, uygulamanızın her seviyesinde doğrulama ve yetkilendirme kontrolleri uygulayın.