ChatGPT sitenizi nasıl okur: llms.txt, .md ayna ve robots.txt

Sıralama vaadi yok. llms.txt HTML ile aynı kaynaktan üretiliyor, her public sayfanın .md sürümü var, bot izinleri robots.txt’de ayrı duruyor. Ne işe yaradığını ve ne yaramadığını kendi sitemizden yazıyoruz.

Bir müşteri “yapay zeka aramasında görünün” diye geldiğinde iki iş birbirine karışıyor. Biri Google’ın klasik tarayıcısı ve arama indeksi. Öbürü ChatGPT, Claude veya Perplexity gibi araçların sayfayı açıp metni yutması. İkisini aynı cümlede satmak kolaydır; ayrı tutmak daha dürüst. Bu yazı ikincisi üzerine: makinenin HTML’inizi gerçekten okuyup okumadığı. Sıralama garantisi yok, Google’da birincilik de yok. Elde kalan şey, okunabilir bir metin iskeleti.

DN Yazılım sitesinde bunları kurduk çünkü kendi işimizi anlatırken “AI-SEO yaptık” demek istemedik. Dosyayı açınca görünsün diye /llms.txt, /blog.md ve /robots.txt canlı duruyor. AI görünürlük sayfası da aynı işin insan dilindeki özeti.

Botlar aynı şey değil

robots.txt bir izin listesidir, rütbe listesi değil. User-agent adını yanlış yazarsanız kural uygulanmaz. Bizde iki grup var. Birincisi herkes (*): site açık, /admin/ ve /api/ kapalı. İkincisi adlandırılmış yapay zeka tarayıcıları. Sitemizdeki kural kabaca şöyle:

User-Agent: GPTBot
User-Agent: ChatGPT-User
User-Agent: Google-Extended
User-Agent: anthropic-ai
User-Agent: ClaudeBot
User-Agent: PerplexityBot
Allow: /
Allow: /blog/
Allow: /hizmetler
Allow: /urunler
Allow: /hakkinda
Allow: /bolgeler
Allow: /isler/
Allow: /ai-gorunurluk
Allow: /llms.txt
Disallow: /admin/
Disallow: /api/

Burada sık karışan üç isim var.

Googlebot arama indeksidir. Yukarıdaki listede yok çünkü * grubuna girer. Sitenin Google’da çıkması bu satıra bağlı değil; Google-Extended’a da bağlı değil.

Google-Extended Gemini’nin eğitim ve genişletme tarayıcısıdır. Arama sonucu üretmez. Biz izin verdik; istemeyenler bu user-agent’ı kapatır. Kararı bilinçli tutun, çünkü “AI’da görünmek” ile “modelimin eğitim setine girmek” aynı şey değil.

GPTBot OpenAI’nin eğitim tarayıcısıdır. ChatGPT-User ise birinin sohbette “şu adrese bak” demesiyle gelen istek. İkisi ayrı durur: eğitimini kapatıp sohbet taramasını açık bırakmak mümkün, tersini de.

Listede olmayan bir ad da var: ChatGPT’nin arama tarayıcısı olarak duyurulan OAI-SearchBot. Biz henüz ayrı kural yazmadık. Yokluğu “ChatGPT bizi görmüyor” demek değil; * zaten public sayfaları açık tutuyor. Ayrı grup, ince ayar içindir.

Cloudflare loglarında son hafta bu sınıftan birkaç yüz istek gördük; “AI Search” ve “AI Crawler” diye etiketlenenler. Dosyalar teoride kalmıyor, birileri çekiyor. Hangi modelin cevabında geçtiğimizi buradan bilemezsiniz. Onu iddia etmiyoruz.

llms.txt: dizin, kopya site değil

Jeremy Howard’ın llmstxt.org taslağı basit: kökte /llms.txt, Markdown, sitenin kısa haritası. Resmi Google sinyali değil ve bir sıralama faktörü olarak satılmaz. Bazı araçlar bu dosyayı “site ne hakkında, hangi URL’ler önemli” diye okuyor; okumayanlar da var. O yüzden dosyayı tek başına “yapay zeka SEO’su” diye paketlemiyoruz.

Bizimki statik bir txt değil. Next.js route’u, HTML sayfalarının beslendiği aynı TypeScript kaynaklarından üretiyor: hizmet listesi, ürünler, portföy, bölgeler, SSS. Bir hizmet ekleyince hem /hizmetler hem /llms.txt aynı kaynaktan güncelleniyor. Ayrı bir “AI için özet” dosyası tutarsanız üç ayda yalan söyler.

// src/app/llms.txt/route.ts (özet)
export async function GET() {
  const content = [
    "# DN Yazılım",
    "## Hizmetler",
    ...services.map((s) => `- **${s.title}**: ${s.summary}`),
    "## Seçili İşler",
    ...projects.map((p) => `- **${p.title}** (${p.domain})`),
  ].join("\\n"); // satır sonu

  return new Response(content, {
    headers: {
      "Content-Type": "text/plain; charset=utf-8",
      "Cache-Control": "public, max-age=3600",
    },
  });
}

text/plain bilinçli. Bazı istemciler text/markdown bekler; düz metin her yerde açılır. Cache bir saat. SSS ve iletişim gibi sık değişmeyen iskelet için yeter. Blog yazısı yayına alınca llms.txt içinde yazı tek tek listelenmiyor; blog indeksine işaret ediyoruz. Her slug’ı buraya basmak dosyayı şişirir. Uzun yazının kendisi /blog/slug.md tarafında.

.md ayna: ikinci CMS yok

HTML’in yanında ayrı Markdown dosyası tutmak bakımı ikiye böler. Biz URL’nin sonuna .md eklenince aynı içeriği Markdown’a çeviriyoruz. /hizmetler/websiteler.md, /isler/motoriders.md, anasayfa için /index.md.

Middleware bakıyor: path .md ile bitiyorsa isteği /md/... route’una rewrite ediyor. Tarayıcı adres çubuğunda hâlâ /blog.md duruyor. 302 yok. Kanonik adres HTML.

// src/middleware.ts
const segments = mdPathSegments(request.nextUrl.pathname);
if (segments) {
  const url = request.nextUrl.clone();
  url.pathname = segments.length === 0 ? "/md" : "/md/" + segments.join("/");
  return NextResponse.rewrite(url);
}

Cevap başlıkları şunlar:

  • Content-Type: text/markdown; charset=utf-8
  • Cache-Control: public, max-age=3600
  • X-Markdown-Canonical HTML path’i taşır, .md’yi değil

Gövdenin tepesinde YAML var. canonical HTML adresi. Model veya tarayıcı “asıl sayfa bu mu, kopya mı” diye bakarsa HTML’i göstersin diye. source: html da dürüstlük: bu dosya elde yazılmadı, HTML’den üretildi.

---
title: "ChatGPT sitenizi nasıl okur"
canonical: https://dnyazilim.com/blog/llms-txt-markdown-ayna-ai-crawler
source: html
---

HTML tarafında da işaret var. buildMetadata her public sayfada markdown alternatifini basıyor:

alternates: {
  canonical: url,
  types: {
    "text/markdown": mdUrl, // örn. https://dnyazilim.com/blog.md
  },
}

Admin, API ve _next bu rewrite’a girmiyor. Rastgele /wp-admin.md de 404 Markdown döner; WordPress sayfası değil.

Blog yazısının HTML’i D1’de duruyor. Markdown üretirken Turndown ile HTML’i çeviriyoruz. Başlık hiyerarşisi ve linkler korunuyor; CSS sınıfı ve animasyon gitmiyor. Gitmesi gereken de o: modele layout lazım değil, cümle lazım.

JSON-LD: accordion kapalıyken de cevap dursun

Şema sihir değil. Yanlış type, boş name veya sahte yıldız gördüğünde Google bunları yok saymayı öğrendi. Bizde gerçek sayfa tipleri duruyor: Organization, BlogPosting, FAQPage, ProfessionalService, BreadcrumbList. Blog yazısında kelime sayısı HTML’den etiketler soyularak hesaplanıyor; uydurma 2000 kelime yazmıyoruz.

SSS tarafı ayrı bir tuzak. Accordion’u client’ta açıp cevabı fetch ederseniz kaynak HTML’de cevap yoktur. Bot JavaScript çalıştırmazsa soru kalır, cevap kaybolur. Bizde cevap ilk HTML’de duruyor. Kapalı panel CSS ile max-height: 0 ve opacity: 0; DOM’dan silinmiyor. Üstüne FAQPage JSON-LD de var. Script çalışmayan istemci JSON-LD’yi alır; çalışan istemci metni de görür.

Kapalı panelde aria-hidden var; erişilebilirlik için. “Google aria-hidden içeriği asla okumaz” diye bir kural yok; pratikte ilk HTML’deki metin çoğu zaman indekslenir. Yine de şemayı bırakmıyoruz. İki kanal, tek kaynak.

IndexNow: Google’a değil

Yazı yayına alınca admin API IndexNow’a haber veriyor. Endpoint api.indexnow.org. Anahtar public bir txt dosyasında; sahiplik kanıtı, gizli secret değil. Bing ve Yandex tarafında “bu URL değişti” bildirimi. Google aynı protokolü resmi olarak aynı şekilde kullanmıyor. “Yayınladık, Google bir dakikada aldı” iddiası buradan çıkmaz. Cloudflare cache’i de ayrı iş; IndexNow CDN’i boşaltmaz.

if (data.status === "published") {
  notifyIndexNow([
    "https://dnyazilim.com/blog",
    "https://dnyazilim.com/blog/" + slug,
  ]);
}

Ne işe yaramaz

llms.txt tek başına sizi ChatGPT’nin cevabına sokmaz. Modelin sizi anması tarama, alıntı, marka araması ve başka sitelerdeki bahislerin karışımı. Bunu ölçmek için Search Console yetmez. Kendi adınızı araçlara yazıp bakarsınız; anekdot kalır.

Markdown ayna, JavaScript’siz tarayıcıya metin verir. İçerik zayıfsa Markdown da zayıf olur. “.md ekledik” bir içerik stratejisi değil.

robots.txt allow, eğitim tarayıcısına “gel” demektir. Ticari sır, fiyat listesi ve müşteri paneli oraya konmaz. Bizim allow listesi blog, hizmet, işler ve bölgeler; admin kapalı.

Başka sitede kopyalamak

Public sayfalar sunucu tarafında tam metin üretsin; içeriği tıklanınca yüklemeyin. robots.txt’de hangi botun eğitim, hangisinin sohbet, hangisinin arama olduğunu ayırın ve isimleri dokümandan kopyalayın, ezbere yazmayın. Kökte /llms.txt olsun, HTML’in kaynağından üretilsin, elle ikinci bir özet tutmayın. İsterseniz .md ayna ekleyin ama canonical HTML’de kalsın. JSON-LD sayfadaki gerçek metinle çelişmesin.

Bunları yaptıktan sonra hâlâ “neden ChatGPT bizi söylemiyor” diye sorulursa cevap genelde içerik ve atıftır, dosya eksiği değil. Kendi sitemizde dosyalar duruyor. Sizin sitede de duruyorsa konuşuruz; durmuyorsa önce onu kuralım. Cloudflare mimari veya iletişim.