robots.txt Rehberi: RFC 9309, Allow/Disallow Kuralları ve Neyi Engellememelisin?
robots.txt crawl'ı kontrol eder, index'i değil: crawler kendi adını taşıyan tek group'u izler, en uzun eşleşen kural kazanır, 5xx dönen bir robots.txt crawl'ı durdurur. RFC 9309 kurallarını C# ile test ediyor, ASP.NET Core'da environment'a göre robots.txt üretiyor, Angular SSR'ın robots.txt'ye bir yıllık cache header'ı verdiğini ve mertozen.com'un dosyasını inceliyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10 (ASP.NET Core 10) ve Angular 22.2 ile derlenip denenmiştir; kurallar RFC (Request for Comments) 9309 ve Google'ın robots.txt dokümanının Kasım 2026 hâli esas alınarak anlatılmıştır.
Önce Tahmin Et
Bookstore'un robots.txt dosyası şöyle. Bir ekip arkadaşı "hesap sayfalarını Googlebot'tan ayrıca saklayalım" diye en alta ikinci bir group eklemiş:
User-agent: *
Disallow: /search
Allow: /search/help
Disallow: /*sort=
Disallow: /*.pdf$
User-agent: Googlebot
Disallow: /account
Üç soru. Googlebot /search?q=dune'u crawl edebilir mi? Bingbot /Search?q=dune'u crawl edebilir mi? Bingbot /files/catalog.pdf?v=2'yi crawl edebilir mi? Cevapları yazının ortasında, gerçek bir eşleştiricinin çıktısıyla göreceğiz; tahminini bir kenara yaz.
Serinin ilk yazısında URL (Uniform Resource Locator) tasarımını kurmuştuk. Şimdi o URL'lerden hangilerinin crawl edileceğine karar veren dosyaya geçiyoruz. robots.txt yirmi yıldan uzun süre fiilî bir standart olarak yaşadı; Eylül 2022'de RFC 9309 ile REP (Robots Exclusion Protocol) adıyla IETF (Internet Engineering Task Force) standardı oldu. Yazarlar arasında Google'dan üç kişi var ve Google'ın robots.txt dokümanı RFC'nin bıraktığı boşlukları kendi davranışıyla dolduruyor. İkisini birlikte okuyacağız.
Dosyanın Yeri, Kapsamı ve Biçimi
RFC 9309'a göre kurallar sitenin kökünde, tamamı küçük harfle /robots.txt adlı dosyada bulunur. Google dokümanı kapsamı netleştiriyor: dosya yalnızca sunulduğu protocol, host ve port üçlüsü için geçerli. https://bookstore.example/robots.txt, https://api.bookstore.example'ı ya da http://bookstore.example'ı kapsamaz; her subdomain'in kendi dosyası olmalı. Alt klasöre konan bir robots.txt'yi crawler'lar hiç okumaz.
Biçim düz metin, kodlaması UTF-8 (Unicode Transformation Format, 8-bit). Satırlar alan: değer biçiminde, # sonrası yorum. Google'ın tanıdığı alanlar dört tane: user-agent, allow, disallow ve sitemap. crawl-delay gibi diğer alanlar Google için yok hükmünde; Google crawl hızını sunucunun yanıt süresine ve hata oranına göre kendisi ayarlıyor. Alan adları büyük/küçük harfe duyarsız, path değerleri ise duyarlı ve / ile başlamalı.
Boyut sınırı RFC'de "en az 500 kibibyte parse edilmeli" diye geçiyor; Google bu sınırı uyguluyor ve 500 KiB'den sonrasını yok sayıyor. Elle yazılmış bir dosya bu sınıra yaklaşmaz; ama otomatik üretilen, her ürün için ayrı satır basan bir robots.txt yaklaşabilir. Satır sayısı bu kadar büyüyorsa yanlış aracı kullanıyorsun demektir: tek tek URL engellemek yerine path pattern'leri kullan.
Group'lar ve User-Agent Eşleşmesi
Bir group, bir veya daha fazla User-agent satırıyla başlar ve altındaki kurallardan oluşur. Crawler kendi product token'ını (Googlebot, Bingbot) büyük/küçük harfe duyarsız olarak group'larla eşleştirir. RFC 9309'un kuralı açık: aynı crawler'ı adlandıran birden fazla group varsa kuralları birleştirilir; hiçbiri eşleşmezse * group'u uygulanır. Kritik nokta ikinci cümlenin tersi: crawler'ın kendi adını taşıyan bir group varsa * group'u tamamen devre dışı kalır, iki group birleştirilmez. Google dokümanı da aynısını söylüyor: her crawler yalnız kendisine en spesifik şekilde eşleşen group'u izler.
Açılıştaki dosyada tam bu oluyor. Ekip arkadaşı Googlebot'a ayrı bir group açtığı anda Googlebot artık Disallow: /search, Disallow: /*sort= ve Disallow: /*.pdf$ kurallarını görmüyor; yalnız Disallow: /account'u görüyor. "Ek bir kısıt" niyetiyle yazılan satır, Googlebot için önceki bütün kısıtları kaldırdı.
Kuralların Önceliği: En Spesifik Eşleşme
Group seçildikten sonra path'e uyan kurallar arasından RFC'nin deyişiyle "en spesifik" olan, yani en çok octet içeren kural kazanır. Allow ve Disallow eşit uzunluktaysa Allow kullanılır. Sıra önemli değil; dosyanın başındaki kural sonundakini ezmez. İki özel karakter var: * sıfır veya daha fazla herhangi bir karakter, $ ise URL'nin sonu. Eşleştirme path ve query string üzerinde yapılır ve büyük/küçük harfe duyarlıdır.
Tahminleri kontrol etmek için RFC 9309'un bu kurallarını küçük bir C# class'ında uyguladım. Gerçek bir parser'ın bütün köşelerini kapsamıyor (percent-encoding normalizasyonu ve uzunluğu octet yerine karakterle sayma gibi), ama group seçimi ve öncelik mantığı aynı:
public bool IsAllowed(string productToken, string path)
{
string token = productToken.ToLowerInvariant();
// RFC 9309: bu token'ı adlandıran tüm group'lar birleşir; hiçbiri yoksa "*"
var own = _groups.Where(g => g.Agents.Contains(token)).ToList();
var groups = own.Count > 0 ? own : _groups.Where(g => g.Agents.Contains("*")).ToList();
List<RobotsRule> rules = groups.SelectMany(g => g.Rules).ToList();
RobotsRule? best = null;
foreach (RobotsRule rule in rules.Where(r => Matches(r.Path, path)))
{
// en uzun pattern kazanır; eşitlikte allow kazanır
if (best is null || rule.Path.Length > best.Path.Length
|| (rule.Path.Length == best.Path.Length && rule.Allow))
best = rule;
}
return best?.Allow ?? true;
}
private static bool Matches(string pattern, string path)
{
string regex = "^" + Regex.Escape(pattern).Replace(@"\*", ".*");
if (regex.EndsWith(@"\$")) regex = regex[..^2] + "$";
return Regex.IsMatch(path, regex);
}
Parse tarafı basit: ardışık User-agent satırları aynı group'a eklenir, bir kural satırından sonra gelen User-agent yeni group başlatır, boş Disallow: hiçbir şeyi engellemez. Açılıştaki dosyayla çalıştırınca:
$ dotnet run --no-launch-profile -- check
Googlebot /search?q=dune allowed
Googlebot /account/orders blocked
Bingbot /search?q=dune blocked
Bingbot /search/help allowed
Bingbot /books?genre=scifi&sort=price blocked
Bingbot /books?genre=scifi allowed
Bingbot /Search?q=dune allowed
Bingbot /files/catalog.pdf blocked
Bingbot /files/catalog.pdf?v=2 allowed
Üç cevap da çoğu kişinin ilk tahmininin tersi. Googlebot /search'e girebiliyor, çünkü kendi group'u var. Bingbot /Search'e girebiliyor, çünkü path eşleştirmesi büyük/küçük harfe duyarlı ve /search kuralı /Search'ü kapsamıyor; serinin ilk yazısındaki küçük harf normalizasyonu burada da işe yarıyor. /files/catalog.pdf?v=2 ise engellenmiyor, çünkü $ URL'nin sonunu işaretliyor ve query string de URL'nin parçası. /search/help örneği ise öncelik kuralının ders kitabı hâli: Allow: /search/help (12 karakter), Disallow: /search'ten (7 karakter) daha spesifik.
Gerçek bir sistemde kendi parser'ını yazma; Google'ın kendi kullandığı C++ robots.txt parser'ı açık kaynak (google/robotstxt repository'si) ve Search Console'da robots.txt raporu, Google'ın dosyanı en son ne zaman çektiğini ve parse ederken ne gördüğünü gösteriyor. Bu class'ın amacı kuralları ezberlemek değil, bir değişikliğin etkisini yayından önce test edilebilir hâle getirmek.
Status Code'lar: robots.txt'nin Kendi Yanıtı
Crawler'ın davranışını dosyanın içeriği kadar, dosyayı isterken aldığı HTTP (HyperText Transfer Protocol) status code'u da belirler. RFC 9309 ve Google dokümanı birlikte:
| robots.txt yanıtı | RFC 9309 | Google'ın uygulaması |
|---|---|---|
| 2xx | Dosya parse edilir | Aynı |
| 3xx | En az beş ardışık redirect izlenmeli | En fazla beş hop; sonra 404 sayılır |
| 4xx (429 hariç) | Dosya yok sayılır, her şey crawl edilebilir | Aynı; 401/403 ile crawl yavaşlatılamaz |
| 5xx, 429, ağ hatası | Tamamen disallow varsayılır | İlk 12 saat crawl durur; 30 güne kadar son başarılı kopya kullanılır |
Tablonun pratik anlamı: robots.txt'nin 404 dönmesi zararsız, 500 dönmesi değil. Uygulaman robots.txt'yi bir veritabanı sorgusundan üretiyorsa ve veritabanı düştüğünde endpoint 500 dönüyorsa, Google o süre boyunca siteyi crawl etmeyi bırakır. robots.txt'yi statik ya da bağımlılıksız üret. Ayrıca robots.txt isteğinin login sayfasına redirect edilmesi (authentication middleware'inin her şeyi yakalaması) klasik bir kazadır; redirect hedefi HTML (HyperText Markup Language) bir login sayfasıysa crawler orada geçerli kural bulamaz.
Cache: RFC, crawler'ların cache'teki kopyayı genellikle 24 saatten uzun kullanmamasını söylüyor; Google da genelde 24 saat cache'liyor ve bu süreyi Cache-Control header'ındaki max-age'e göre uzatıp kısaltabileceğini belirtiyor. Bunun Angular tarafında somut bir sonucu var, birazdan göreceğiz.
ASP.NET Core'da Environment'a Göre robots.txt
Bookstore'un staging ortamı production ile aynı kodla çalışıyor. robots.txt'yi statik dosya olarak koyarsan staging de production'ın kurallarını yayınlar. Endpoint olarak üretmek, environment'a göre farklılaştırmayı kolaylaştırıyor:
app.MapGet("/robots.txt", (IWebHostEnvironment env, HttpContext context) =>
{
string body = env.IsProduction()
? """
User-agent: *
Disallow: /search
Disallow: /account/
Disallow: /*?*sort=
Sitemap: https://bookstore.example/sitemap.xml
"""
: """
# non-production environment: do not crawl anything
User-agent: *
Disallow: /
""";
context.Response.Headers.CacheControl = "public, max-age=3600";
return Results.Text(body + "\n", "text/plain; charset=utf-8");
});
$ ASPNETCORE_ENVIRONMENT=Production dotnet run --no-launch-profile --urls http://localhost:20103
$ curl -si http://localhost:20103/robots.txt
HTTP/1.1 200 OK
Content-Length: 120
Content-Type: text/plain; charset=utf-8
Cache-Control: public, max-age=3600
User-agent: *
Disallow: /search
Disallow: /account/
Disallow: /*?*sort=
Sitemap: https://bookstore.example/sitemap.xml
$ ASPNETCORE_ENVIRONMENT=Staging dotnet run --no-launch-profile --urls http://localhost:20103
$ curl -si http://localhost:20103/robots.txt
HTTP/1.1 200 OK
Content-Length: 78
Content-Type: text/plain; charset=utf-8
Cache-Control: public, max-age=3600
# non-production environment: do not crawl anything
User-agent: *
Disallow: /
Endpoint bilinçli olarak hiçbir service'e, veritabanına veya configuration okumasına bağlı değil; yukarıdaki tablonun 5xx satırına düşme ihtimali yok. Sitemap satırı mutlak URL olmalı, birden fazla olabilir, başka bir host'u gösterebilir ve hiçbir group'a ait değildir; dosyanın neresine yazdığın fark etmez. Staging'deki Disallow: / önemli ama yeterli değil; neden yeterli olmadığı tuzaklar bölümünde.
Angular'da Statik robots.txt ve Gizli Bir Cache Header'ı
Angular CLI (Command Line Interface) projesinde public/ klasörüne konan her dosya build çıktısının köküne kopyalanır. public/robots.txt yazıp ng build çalıştırınca dosya dist/bookstore-ng/browser/robots.txt olarak çıkıyor ve SSR (Server-Side Rendering) sunucusundaki express.static onu servis ediyor. İlk denemede aldığım yanıt:
$ curl -si http://localhost:20110/robots.txt
HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000
Content-Type: text/plain; charset=utf-8
User-agent: *
Disallow: /search
Disallow: /account
Sitemap: https://bookstore.example/sitemap.xml
max-age=31536000 bir yıl demek. ng new --ssr'ın ürettiği server.ts, hash'li bundle dosyaları için doğru olan maxAge: '1y' ayarını bütün statik dosyalara uyguluyor; robots.txt da bunlardan biri. Google cache süresini max-age'e göre ayarlayabildiğini söylüyor, araya bir CDN (Content Delivery Network) girdiğinde ise bir yıllık header gerçekten bir yıl boyunca eski dosyanın dağıtılması anlamına gelebilir. Düzeltme setHeaders ile:
app.use(
express.static(browserDistFolder, {
maxAge: '1y',
index: false,
redirect: false,
setHeaders: (res, path) => {
// robots.txt ve sitemap.xml bir yıl cache'lenmesin
if (path.endsWith('robots.txt') || path.endsWith('.xml')) {
res.setHeader('Cache-Control', 'public, max-age=3600');
}
},
}),
);
$ curl -sI http://localhost:20110/robots.txt | grep -i cache-control
Cache-Control: public, max-age=3600
Statik dosyanın dezavantajı environment ayrımı: aynı build hem staging'e hem production'a gidiyorsa ikisi de aynı robots.txt'yi yayınlar. Ya deploy sırasında dosyayı değiştir ya da Express tarafında robots.txt'yi bir route ile üret. SPA (Single-Page Application) projelerinde build ve deploy akışı Angular serisinin "Angular Build ve Deploy: ng build, Environment Dosyaları, Budgets ve SPA Routing" yazısında.
robots.txt Crawl'ı Engeller, Index'i Engellemez
En yaygın yanlış anlama bu. Disallow, Googlebot'un URL'yi istemesini engeller; URL'nin index'e girmesini değil. Başka sitelerden açıklayıcı link alan engellenmiş bir URL, içeriği hiç okunmadan sonuçlarda görünebilir; snippet yerine açıklamanın bulunmadığını söyleyen bir not çıkar. Üstelik engellenmiş sayfaya koyduğun noindex hiç görülmez, çünkü crawler sayfayı hiç istemez. Bir sayfayı index'ten çıkarmak istiyorsan crawl'a açık bırakıp noindex vermen gerekir; bunu bir sonraki yazıda X-Robots-Tag ile birlikte ele alıyoruz.
Aynı mantık render için de geçerli. Google sayfayı render ederken CSS (Cascading Style Sheets), JavaScript ve görselleri ayrı ayrı ister; bunlar robots.txt ile engellenmişse render edilen sayfa eksik kalır. Disallow: /assets/ ya da Disallow: /*.js$ gibi bir satır, SPA için içeriğin tamamen görünmez olması demektir.
mertozen.com'un robots.txt'si
Bu sitenin robots.txt'si de bir ASP.NET Core endpoint'inden üretiliyor ve sitenin kaynak kodundaki hâli şu yapıda: önce User-agent: * group'u ve Allow: /, ardından GPTBot, PerplexityBot, ClaudeBot, Google-Extended ve CCBot için ayrı ayrı group'lar, her birinde yine yalnız Allow: /, en sonda mutlak URL'li Sitemap satırı. Hiç Disallow yok.
RFC 9309'a göre bugün bu dosyanın etkisi, yalnız Sitemap satırı olan bir dosyayla aynı: kimse hiçbir şeyden engellenmiyor, Allow: / boş bir dosyanın zaten verdiği izni tekrar ediyor. Ayrı group'lar bugün işlevsiz ama yarın için bir tuzak: bir gün * group'una Disallow: /araclar/ eklenirse, kendi group'u olan beş bot bu kuralı görmez; açılıştaki Googlebot örneğinin aynısı. Bu group'ların bir niyeti de var: sitenin GEO (Generative Engine Optimization) notlarında içeriğin AI (Artificial Intelligence) sistemlerince okunmasının bilinçli olarak istendiği yazıyor. Niyeti korumanın daha az kırılgan yolu, botları ancak *'dan farklı bir kural gerektiğinde ayrı group'a almak. Google-Extended'ın bir crawler değil, ayrı bir kullanım izni token'ı olduğu ve AI bot'larının training, search ve kullanıcı adına fetch ayrımı GEO serisinin "AI Bot'larını Tanımak ve Yönetmek" yazısının konusu. Değişiklik kararı site sahibinin; burada yalnız kuralların nasıl okunacağını gösteriyorum.
Üretimde Dikkat
robots.txt'yi deploy edilen her değişiklikte test et. Yukarıdaki gibi bir eşleştiriciyi CI (Continuous Integration) pipeline'ına koyup "şu URL'ler crawl edilebilir kalmalı" listesini (ana sayfa, kitap sayfaları, CSS ve JavaScript bundle'ları) her build'de kontrol etmek, tek bir yanlış Disallow'un bütün siteyi kapatmasını engeller.
Yayından sonra Search Console'daki robots.txt raporuna bak: Google'ın dosyayı en son ne zaman çektiğini, hangi status code'u aldığını ve parse uyarılarını gösteriyor. Değişikliğin etkisi anında değil, cache süresi kadar gecikmeli gelir.
Dosyayı UTF-8 olarak, BOM (Byte Order Mark) olmadan kaydet. Google dosya başındaki BOM'u ve geçersiz satırları yok sayıyor, ama her crawler'ın parser'ı bu kadar toleranslı değil; ilk satırın başındaki görünmez bir BOM, daha katı bir parser'da ilk User-agent satırını tanınmaz hâle getirebilir. Editörünün yeni dosya için seçtiği encoding'i kontrol et.
Crawl'ı yavaşlatmak için robots.txt kullanma. Crawl-delay Google'da desteklenmiyor; geçici bir aşırı yükte Google 503 veya 429 dönmeni öneriyor, bunu serinin altıncı yazısında göreceğiz.
Sık Düşülen Tuzaklar
Staging'i robots.txt ile "gizlemek". RFC 9309 açıkça söylüyor: bu kurallar bir yetkilendirme biçimi değildir. robots.txt herkese açık bir dosyadır, hatta neyi sakladığını ilan eder; kurallara uymayan bir bot için hiçbir anlamı yoktur. Staging'in gerçek koruması authentication (kimlik doğrulama), IP (Internet Protocol) kısıtı veya VPN (Virtual Private Network). Disallow: / yalnız bunların yanındaki ikinci kemerdir.
Index'ten çıkarmak için Disallow eklemek. Sayfa zaten index'teyse Disallow onu çıkarmaz; üstüne noindex'in görülmesini de engeller.
CSS ve JavaScript'i engellemek. Render edilen sayfa boş kalır, mobil uyumluluk ve içerik değerlendirmesi bozulur.
Bir bota ayrı group açıp * kurallarının ona da uygulanacağını sanmak. Group'lar birleşmez; o bot için gerekli bütün kuralları kendi group'una tekrar yaz.
Büyük/küçük harfi ve $'ı unutmak. Disallow: /Search, /search'ü engellemez; Disallow: /*.pdf$, ?v=2 eklenmiş PDF'i engellemez.
Küçük Bir Deneme
★☆☆ Açılıştaki dosyayı Googlebot group'una * group'undaki kuralları da kopyalayarak düzelt ve eşleştiriciyi tekrar çalıştır. Googlebot için /search?q=dune artık blocked çıkmalı.
★★☆ Disallow: /*.pdf$ kuralını sürüm parametreli PDF'leri de kapsayacak şekilde yeniden yaz ve /files/catalog.pdf?v=2 ile /files/catalog.pdfx için sonucu karşılaştır. Hangi pattern ikisini doğru ayırıyor?
★★★ robots.txt endpoint'i için bir integration test yaz: WebApplicationFactory ile uygulamayı Production ve Staging environment'larında ayağa kaldır, dönen dosyayı RobotsTxt.Parse ile oku ve "kitap sayfaları ve bundle'lar Production'da crawl edilebilir, Staging'de hiçbir şey crawl edilemez" kuralını assert et. WebApplicationFactory'nin kullanımı ASP.NET Core Web API (Application Programming Interface) serisinin "ASP.NET Core Web API'de Integration Test: WebApplicationFactory ile Uçtan Uca Test" yazısında.
robots.txt crawler'ı kapıda durduruyor; ama kapıdan girmesine izin verdiğin bir sayfanın sonuçlarda görünmemesini istiyorsan başka bir mekanizma lazım. Bir sonraki yazıda robots meta tag'i ve X-Robots-Tag header'ını, PDF'leri index dışında tutmayı ve robots.txt ile noindex'in neden birlikte çalışmadığını göreceğiz.