Yönlendirme (redirect) basit görünür: sunucu bir HTTP durum kodu ve bir Location başlığıyla yanıt verir, istemci de o adrese gider. Ancak tarayıcıların yönlendirmeyi önbelleğe alıp alamayacağını, bir POST isteğinin POST olarak kalıp kalmayacağını ve arama motorlarının büyük olasılıkla hangi URL'yi dizine ekleyeceğini o üç rakam belirler.
Bu rehberde RFC 9110'da tanımlandığı şekliyle 3xx sınıfını, kodlar arasındaki örtüşmelerin tarihçesini, durum kodu olmadan yapılan yönlendirmeleri ve yaygın sunucularda doğru kodun nasıl yapılandırılacağını ele alıyoruz.
3xx sınıfı ne anlama gelir?
HTTP semantiğinin güncel spesifikasyonu olan RFC 9110'a göre 3xx durum kodu, “isteğin yerine getirilmesi için kullanıcı aracısının (user agent) ek bir işlem yapması gerektiğini” belirtir. Bölüm 15.4, kodları dört yönlendirme türüne ayırır:
- Kaynak,
Locationbaşlığında belirtilen başka bir URI'de bulunur: 301, 302, 307 ve 308. - İstemci, kaynağın birden fazla temsili arasından seçim yapabilir: 300.
- İsteğe dolaylı bir yanıtı farklı bir kaynak sağlar: 303.
- İstemci, önbelleğinde zaten bulunan bir kopyayı yeniden kullanabilir: 304.
Pratikte yalnızca 301, 302, 303, 307 ve 308 otomatik yönlendirme olarak çalışır. Tarayıcıların uyguladığı Fetch Standard da tam olarak bu beş kodu “redirect status” (yönlendirme durumu) olarak adlandırır. 300 genellikle normal bir sayfa gibi gösterilir; 304 ise istemciye önbelleğindeki kopyayı kullanmasını söyler.
Location değeri /new-page veya ../shop/ gibi göreli bir referans olabilir. İstemci bunu az önce istediği URL'ye göre çözümler (RFC 9110, Bölüm 10.2.2). Location değerinde bir parça (fragment) yoksa orijinal parça korunur: /docs/guide adresine yönlendiren bir /guide#install bağlantısı /docs/guide#install adresinde sona erer.
Beş gerçek yönlendirme kodundan hangisinin uygun olduğuna iki soru karar verir:
- Taşıma kalıcı mı, geçici mi? Kalıcı yönlendirmeler önbelleğe alınabilir ve arama motorlarına eski URL yerine yeni URL'yi dizine eklemeleri gerektiğini bildirir.
- İstek metodu korunmalı mı? Bu, orijinal istek sıradan bir GET değil de POST, PUT veya DELETE olduğunda önem kazanır.
Yönlendirme durum kodlarına genel bakış
| Kod | Ad | Kalıcı | Metot korunur | Varsayılan olarak önbelleğe alınabilir | Tipik kullanım | Google Arama |
|---|---|---|---|---|---|---|
| 300 | Multiple Choices | – | – | Evet | Alternatif temsiller sunmak (nadir) | – |
| 301 | Moved Permanently | Evet | Hayır: POST, GET'e dönüşebilir | Evet | Taşınan sayfalar ve siteler, HTTPS ve kanonik ana makine yönlendirmeleri | Kalıcı (güçlü sinyal) |
| 302 | Found | Hayır | Hayır: POST, GET'e dönüşebilir | Hayır | Kısa süreli yönlendirmeler, giriş zorunluluğu, A/B testleri | Geçici (zayıf sinyal) |
| 303 | See Other | Hayır | Hayır: GET'e dönüşür | Hayır | Form gönderiminden sonra sonuç sayfası | Geçici |
| 304 | Not Modified | Yönlendirme değil | – | – | Koşullu isteğe yanıt | İçerik değişmedi |
| 305 | Use Proxy | Kullanımdan kaldırıldı | – | – | Kullanmayın | – |
| 306 | (Kullanılmıyor) | Ayrılmış | – | – | Kullanmayın | – |
| 307 | Temporary Redirect | Hayır | Evet | Hayır | Her metot için geçici yönlendirmeler | Geçici |
| 308 | Permanent Redirect | Evet | Evet | Evet | API'lerin, formların veya herhangi bir URL'nin kalıcı taşınması | Kalıcı (güçlü sinyal) |
“Varsayılan olarak önbelleğe alınabilir”, kodun RFC 9110, Bölüm 15.1 uyarınca sezgisel olarak önbelleğe alınabilir (heuristically cacheable) olduğu anlamına gelir: yanıt açık bir tazelik bilgisi taşımasa bile önbellek, sunucuya sormadan yanıtı yeniden kullanabilir. Yine de her yanıt Cache-Control ile önbelleğe alınabilir ya da alınamaz hâle getirilebilir. Google sütunu, Google'ın yönlendirmeler ve HTTP durum kodları hakkındaki belgelerini özetler.
Tüm kodlar tek tek
300 Multiple Choices
Kaynağın farklı diller veya dosya biçimleri gibi birden fazla temsili vardır ve istemci bunlar arasından seçim yapabilir. Gövde genellikle seçenekleri listeler, ancak bunun standart bir biçimi yoktur. Tercih edilen seçenek Location başlığında belirtilebilir, ama tarayıcıların onu takip edeceğine güvenemezsiniz. Pratikte 300 neredeyse hiç kullanılmaz: sunucu taraflı içerik anlaşması (content negotiation) ya da bağlantılar içeren sıradan bir sayfa daha iyi sonuç verir.
301 Moved Permanently
301, hedef kaynağa “yeni ve kalıcı bir URI atandığını ve bu kaynağa yapılacak sonraki tüm başvuruların verilen URI'lerden birini kullanması gerektiğini” bildirir. Taşınan sayfalar, değişen alan adları, HTTP'den HTTPS'ye geçişler ve kanonik ana makine yönlendirmeleri için standart kod budur.
İki ayrıntı önemlidir. 301 sezgisel olarak önbelleğe alınabilir; bu yüzden tarayıcılar onu hatırlayıp sunucuya sormayı bırakabilir (aşağıdaki önbellek bölümüne bakın). Ayrıca tarihsel nedenlerle istemci, bir 301'i takip ederken POST'u GET'e çevirebilir ve tarayıcılar bunu yapar. GET ile yüklenen sayfalar için bunun bir önemi yoktur; bir form uç noktası veya API için 308 kullanın.
302 Found
302, hedef kaynağın “geçici olarak farklı bir URI altında bulunduğunu” bildirir; bu yüzden istemciler sonraki isteklerde orijinal URL'yi kullanmaya devam etmelidir. 302 varsayılan olarak önbelleğe alınamaz. Kısa süreli durumlar için kullanın: bir kategori sayfasının yerini bir haftalığına alan indirim sayfası, giriş yapma zorunluluğu, coğrafi yönlendirme veya bir A/B testi.
301 gibi 302 de POST'tan GET'e geçişe izin verir. Birçok framework, kod belirtilmediğinde 302 gönderir; kalıcı taşımaların bu kadar sık yanlışlıkla 302 olarak kalmasının nedeni budur.
303 See Other
303, istemciyi orijinal isteğe dolaylı yanıt işlevi gören farklı bir kaynağa gönderir; istemci bu kaynağı GET (veya HEAD) ile alır. Post/Redirect/Get kalıbı için tasarlanmıştır: sunucu bir form gönderimini işledikten sonra, bir onay veya sonuç sayfasının URL'siyle birlikte 303 yanıtı verir. Kullanıcı o sayfayı yenilediğinde form yeniden gönderilmez, yalnızca zararsız bir GET isteği tekrarlanır.
303 varsayılan olarak önbelleğe alınamaz ve orijinal URL'nin taşındığını iddia etmez. Google onu bir 302 gibi değerlendirir.
304 Not Modified (yönlendirme değildir)
3xx sınıfında olmasına rağmen 304, istemciyi hiçbir yere göndermez. Koşullu bir GET veya HEAD isteğini yanıtlar; yani bir ETag değeriyle If-None-Match ya da bir tarihle If-Modified-Since başlığı taşıyan bir isteği. Kaynak değişmemişse sunucu gövdesiz bir 304 ile yanıt verir ve istemci önbelleğindeki kopyayı kullanır.
GET /styles.css HTTP/1.1
Host: example.com
If-None-Match: "5f1c-3a2b"
HTTP/1.1 304 Not Modified
ETag: "5f1c-3a2b"
Cache-Control: max-age=3600Yönlendirme kontrol araçları koşulsuz istek gönderdiği için normalde hiç 304 görmez. TrueURL'de bir 304'ü test etmek için normal bir kontrol yapın, yanıt başlıklarından ETag değerini kopyalayın ve Özel istek başlıkları bölümünde If-None-Match başlığı olarak gönderin. Googlebot için 304, içeriğin son taramadan bu yana değişmediği anlamına gelir.
305 Use Proxy ve 306 (Kullanılmıyor)
305, istemciye isteği Location başlığında belirtilen proxy üzerinden tekrarlamasını söylüyordu. Bir yanıtın istemcinin proxy ayarını değiştirebilmesi bir güvenlik riski olduğundan RFC 7231 bu kodu kullanımdan kaldırdı; güncel tarayıcılar da bu koda göre hareket etmez.
306, spesifikasyonun önceki bir sürümünde tanımlanmıştı (çoğu zaman Switch Proxy olarak listelenir) ve artık ayrılmış durumdadır. Bu iki kodun da modern bir uygulamada yeri yoktur; birini görüyorsanız bir yerde yapılandırma hatası var demektir.
307 Temporary Redirect
307, 302 ile aynı anlama gelir ama bir farkı vardır: istemci istek metodunu değiştirmemelidir. Bir POST, aynı gövdeyle yeni URL'ye yine POST olarak gönderilir. Bu da 307'yi API'ler ve form uç noktaları için doğru geçici yönlendirme yapar.
307 ile Chrome DevTools'ta http:// adresinden https:// adresine bir 307 Internal Redirect olarak da karşılaşabilirsiniz. Bunu sunucu hiç göndermemiştir. Tarayıcı isteği kendisi yükseltmiştir; bunun nedeni genellikle site için kaydettiği bir HSTS politikasıdır.
308 Permanent Redirect
308, 301'in metodu koruyan karşılığıdır: kalıcıdır, sezgisel olarak önbelleğe alınabilir ve istemci metodu değiştirmemelidir. İlk kez 2014'te deneysel RFC 7238 ile yayımlandı, 2015'te RFC 7538 ile standart hâline geldi ve bugün RFC 9110'un bir parçasıdır.
POST, PUT veya DELETE isteği alan uç noktaların kalıcı taşınmalarında 308 kullanın. Sıradan sayfalar için 301 ve 308 birbirinin yerine kullanılabilir; Google ikisini de kalıcı yönlendirme olarak değerlendirir.
303, 307 ve 308 neden var?
301/302 ile 303/307/308 arasındaki örtüşme, tarihsel bir tesadüfün sonucudur.
HTTP/1.0 (RFC 1945, 1996), 301 Moved Permanently ve 302 Moved Temporarily kodlarını tanımladı. Bu spesifikasyon, kullanıcı aracısının bir POST isteğini kullanıcının onayı olmadan otomatik olarak yönlendirmemesi gerektiğini söylüyor ve bazı kullanıcı aracılarının POST'u yanlışlıkla GET'e çevirdiğini daha o zaman belirtiyordu. Tarayıcılar bunu yapmaya devam etti.
HTTP/1.1, yoruma yer bırakmayan iki kod ekledi. RFC 2068 (1997), her zaman GET'e geçen 303 See Other kodunu getirdi. RFC 2616 (1999) ise metodu her zaman koruyan 307 Temporary Redirect kodunu ekledi, 302'nin adını Found olarak değiştirdi ve çoğu kullanıcı aracısının 302'ye 303 gibi davrandığını açıkça belirtti.
Yine de metodu koruyan kalıcı bir yönlendirme hâlâ yoktu. Bu boşluğu on beş yıl sonra 308 doldurdu. Bu arada spesifikasyon da gerçeği kabullendi: RFC 7231'den (2014) bu yana kullanıcı aracısı, bir 301 veya 302'den sonra POST'u GET'e çevirebilir.
Bugün tarayıcılar Fetch Standard belgesindeki şu kuralları uygular:
| Durum kodu | Orijinal metot | Tarayıcıların yeni URL'ye gönderdiği istek |
|---|---|---|
| 301, 302 | POST | GET, gövde olmadan |
| 301, 302 | Diğer tüm metotlar | Aynı metot |
| 303 | GET ve HEAD dışındaki tüm metotlar | GET, gövde olmadan |
| 303 | GET veya HEAD | Aynı metot |
| 307, 308 | Tüm metotlar | Aynı metot ve gövde |
301 veya 302'den sonra yalnızca POST değiştirilir; PUT veya DELETE metodunu korur. HTTP kütüphaneleri farklı davrandığı için buna güvenmek risklidir; metot önemli olduğunda her zaman 307 veya 308 kullanın.
3xx durum kodu olmadan yapılan yönlendirmeler
Meta refresh
<meta http-equiv="refresh" content="0; url=https://example.com/new-page">Sayfanın kendisi 200 OK ile gelir ve tarayıcı sayfayı yükledikten sonra etiketi takip eder. Sayı, saniye cinsinden gecikmedir. Google anında çalışan bir meta refresh'i kalıcı yönlendirme, gecikmeli olanı ise geçici yönlendirme olarak yorumlar.
Zamanlı yenilemeler erişilebilirliğe de zarar verir. WCAG, anında çalışan bir meta refresh'i istemci taraflı yönlendirme olarak kabul eder (H76 tekniği), ancak zamanlı olanı bir başarısızlık olarak değerlendirir (F40), çünkü sayfa kullanıcıların kontrol edemediği bir zamanlayıcıyla değişir.
JavaScript yönlendirmeleri
<script>
window.location.replace('https://example.com/new-page');
</script>location.replace() yönlendiren sayfayı oturum geçmişinde bırakmaz; bu yüzden Geri düğmesi kullanıcıları yeniden yönlendirmenin içine düşürmez. location.href değerine atama yapmak ise sayfayı geçmişte bırakır.
Google JavaScript'i yalnızca bir sayfayı taradıktan sonra, sayfayı oluştururken (rendering) çalıştırır ve oluşturma başarısız olabilir. Google'ın belgeleri, JavaScript yönlendirmelerini yalnızca sunucu taraflı ve meta refresh yönlendirmeleri mümkün olmadığında önerir. Diğer birçok bot ve araç JavaScript'i hiç çalıştırmaz.
Refresh başlığı
HTTP/1.1 200 OK
Content-Type: text/html
Refresh: 0; url=https://example.com/new-pageRefresh başlığı hiçbir HTTP RFC'sinde tanımlanmamıştır. Tarayıcılar onu onlarca yıldır destekler ve HTML Standard bu başlığı bir meta refresh etiketiyle eşdeğer kabul eder. curl dahil birçok HTTP istemcisi ve aracı bu başlığı yok sayar; bu yüzden gerçek bir 3xx yönlendirmesini tercih edin.
Yönlendirmeler doğru şekilde nasıl uygulanır?
nginx
# Her iki ana makinedeki tüm http:// istekleri tek adımda kanonik HTTPS URL'sine gider
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# https://www.example.com -> https://example.com
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/ssl/example.com.crt;
ssl_certificate_key /etc/ssl/example.com.key;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
# ssl_certificate ve ssl_certificate_key yukarıdaki gibi
# Taşınmış tek bir sayfa
location = /old-page {
return 301 /new-page;
}
# Yolun geri kalanını koruyarak bütün bir bölüm
location /blog/ {
rewrite ^/blog/(.*)$ /articles/$1 permanent;
}
}$request_uri orijinal yolu ve sorgu dizesini içerir, böylece hiçbir şey kaybolmaz. rewrite ... permanent bir 301, rewrite ... redirect ise bir 302 gönderir; 307 veya 308 için return yönergesini o kodla kullanın.
Apache (.htaccess)
# mod_alias: tek bir sayfa ve bütün bir klasör (yolun geri kalanı sona eklenir)
Redirect 301 /old-page https://example.com/new-page
Redirect 301 /blog/ https://example.com/articles/
# Düzenli ifadeyle mod_alias
RedirectMatch 301 ^/products/([0-9]+)\.html$ https://example.com/p/$1
# mod_rewrite: tüm http:// ve www isteklerini tek adımda https://example.com adresine gönderir
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]Durum kodu belirtilmeyen Redirect bir 302 gönderir, bu yüzden kodu her zaman yazın. Apache'ye düz HTTP ile bağlanan bir yük dengeleyici veya CDN arkasında %{HTTPS} her zaman kapalıdır ve son kural döngüye girer; bunun yerine %{HTTP:X-Forwarded-Proto} değerini kontrol edin. mod_alias ve mod_rewrite birbirinden bağımsız çalışır ve dosyadaki satırların sırası hangi modülün önce çalışacağını belirlemez. Buradaki http://www.example.com/old-page gibi her iki modüldeki kurallarla eşleşen bir URL iki adımda (iki ayrı istek ve yanıtla) yönlendirilebilir; tek adımda yönlendirilmesi gereken URL'lerin tüm kurallarını tek bir modülde tutun.
Cloudflare
Cloudflare, istek sunucunuza ulaşmadan önce uç ağda (edge) yönlendirme yapabilir:
Single Redirects (Cloudflare panelindeki Redirect Rules bölümü), istekleri bir filtre ifadesiyle eşleştirir ve 301, 302, 303, 307 veya 308 ile statik ya da dinamik bir hedefe gönderir. www'den kök alan adına (apex) yönlendiren tipik bir kural şu ayarlara sahiptir:
When incoming requests match: (http.host eq "www.example.com") URL redirect type: Dynamic Expression: concat("https://example.com", http.request.uri.path) Status code: 301 Preserve query string: OnBulk Redirects, CSV olarak yüklenen bir taşıma haritası gibi uzun kaynak ve hedef URL listelerini işler. Her girdinin kendi durum kodu ve sorgu dizesini koruma gibi seçenekleri vardır; listeyi bir Bulk Redirect Rule etkinleştirir.
Workers,
Response.redirect()ile yönlendirmeleri kodda oluşturabilir:
export default {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === '/old-page') {
url.pathname = '/new-page'; // sorgu dizesi korunur
return Response.redirect(url.toString(), 301);
}
return fetch(request);
},
};Response.redirect() varsayılan olarak 302 kullanır ve yalnızca 301, 302, 303, 307 ve 308 kodlarını kabul eder. Başka herhangi bir kod RangeError hatası fırlatır.
Node.js ve Express
import express from 'express';
const app = express();
app.set('trust proxy', 1); // bir yük dengeleyici veya CDN arkasında çalışırken
// HTTPS ve kanonik ana makine tek adımda
app.use((req, res, next) => {
if (req.secure && req.hostname === 'example.com') return next();
res.redirect(301, `https://example.com${req.originalUrl}`);
});
// Taşınmış tek bir sayfa
app.get('/old-page', (req, res) => {
res.redirect(301, '/new-page');
});Durum kodu verilmeyen res.redirect(url) bir 302 gönderir. Express 5 yalnızca res.redirect(status, url) sıralamasını destekler. Saf Node.js'te durum kodunu ve başlığı kendiniz ayarlayın:
res.writeHead(301, { Location: 'https://example.com/new-page' });
res.end();PHP
<?php
// Herhangi bir çıktı gönderilmeden önce çalışmalıdır.
header('Location: https://example.com/new-page', true, 301);
exit;Tek başına bir Location başlığı, PHP'nin 302 göndermesine yol açar (daha önce 201 veya bir 3xx durum kodu ayarlanmamışsa). Bu yüzden kodu üçüncü argüman olarak verin ve başka hiçbir şey çalışmaması için exit kullanın. WordPress'te wp_redirect() ve wp_safe_redirect() da varsayılan olarak 302 kullanır; ikinci argüman olarak 301 verin.
Doğru yönlendirme kodunu seçmek
- Sayfa kalıcı olarak taşındıysa ve GET ile yüklenen sıradan bir sayfaysa: 301 kullanın. 308 de aynı şekilde iş görür.
- Taşıma kalıcıysa ve API'lerde, form action adreslerinde ve webhook'larda olduğu gibi istemciler POST, PUT veya DELETE gönderebiliyorsa: 308 kullanın.
- Yönlendirme geçiciyse ve yalnızca GET önemliyse: 302 kullanın.
- Yönlendirme geçiciyse ve metot korunmalıysa: 307 kullanın.
- Bir form veya başka bir POST isteği işlendiyse ve kullanıcı bir sonuç sayfası görmeliyse: 303 kullanın.
- HTTP'den HTTPS'ye: 301 veya 308 kullanın ve HTTPS sitesinden bir
Strict-Transport-Securitybaşlığı gönderin; böylece tarayıcılar bir dahaki sefere güvensiz isteği atlar. - İçerik kaldırıldıysa ve eşdeğeri yoksa: yönlendirme yapmayın. 404 veya 410 döndürün. Google, ana sayfa gibi alakasız bir sayfaya yapılan yönlendirmeyi soft 404 olarak değerlendirebilir.
- 300, 305 ve 306: bunları kullanmayın. 304 ise koşullu isteklere yanıt olarak sunucunuz veya önbelleğiniz tarafından üretilir ve hiçbir zaman bir yönlendirme seçeneği değildir.
301 ve 308'in önbellek tuzağı
301 ve 308 sezgisel olarak önbelleğe alınabildiği için tarayıcılar bunları saklayıp sunucuya sormayı bırakabilir. Chromium tabanlı tarayıcılar, önbellek başlıkları olmayan bir 301 veya 308'i, kayıt önbellekten atılana ya da kullanıcı önbelleği temizleyene kadar saklar. Yanlış bir kalıcı yönlendirme, siz sunucuyu düzelttikten çok sonra bile eski ziyaretçileri yanlış yere göndermeye devam edebilir ve bu yönlendirmeyi ziyaretçilerin tarayıcılarından güvenilir bir şekilde geri çekemezsiniz.
Güvenli tarafta kalmak için:
- Yeni kuralları önce 302 veya 307 ile test edin, hedef doğrulandıktan sonra 301 veya 308'e geçin.
- Alternatif olarak, kalıcı yönlendirmeyi devreye alırken ne kadar süre önbelleğe alınabileceğini sınırlayın:
location = /old-page {
add_header Cache-Control "max-age=3600";
return 301 https://example.com/new-page;
}- Test ederken gizli pencere, Disable cache seçeneği açık DevTools ya da hiçbir zaman tarayıcı önbelleği kullanmayan sunucu taraflı bir kontrol aracı kullanın.
- Bir yönlendirmeyi değiştirdikten sonra CDN ve proxy önbelleklerini temizleyin, çünkü onlar da yönlendirmeleri saklayabilir.
- Önbelleğe alınmış bir A → B yönlendirmesini, B'yi tekrar A'ya yönlendirerek geri almaya çalışmayın. Tarayıcısı ilk yönlendirmeyi önbelleğe almış ziyaretçiler bir döngüye girebilir.
Yönlendirmelerinizi test etme
Hangi platformu kullanırsanız kullanın, sonucu dışarıdan doğrulayın. 301 yönlendirme kontrolü aracı kalıcı bir taşımanın her adımının 301 veya 308 olduğunu doğrular; Yönlendirme zinciri kontrolü her adımı Location değeri ve başlıklarıyla birlikte gösterir; HTTPS yönlendirme kontrolü ise protokol ve ana makine açısından dört sürümün hepsini aynı anda test eder. Meta refresh kontrolü yalnızca başlıklara bakan araçların gözden kaçırdığı istemci taraflı yönlendirmeleri bulur, Yönlendirme gecikmesi kontrolü de her adımın ne kadar süre kattığını ölçer.
Terminalde curl -sIL https://example.com/old-page komutu her adımın başlıklarını yazdırır. URL yönlendirmelerinin nasıl kontrol edileceğini anlatan rehberimiz komut satırını, DevTools'u ve yaygın yönlendirme sorunlarını ayrıntılı olarak ele alıyor.