
Blogumun Evrimi #2: Yapay Zeka Ajanı Optimizasyonu
Blogumun evrimindeki ikinci adımda, yeni dünyanın önemli bir parçası haline gelen yapay zeka ajanlarına ve sitelerimizin bu yeni ziyaretçilere ne kadar hazır olduğuna odaklanacağım.
Bugüne kadar web sitelerimizi SEO için arama motorlarının botları, okuma deneyimi içinse insanlara uygun şekilde optimize ettik. Ancak artık sadece okumakla kalmayıp karar veren, bilgi sentezleyen, işlem yapan ve interneti bizim adımıza “deneyimleyen” otonom yapılar (AI Agents) var. Eğer dijital varlığınız sadece insanların tıklaması ve kaydırması için optimize edilmiş ise internetin bu yeni ve agresif büyüyen kullanıcı kitlesine kapılarınızı kapatıyorsunuz demektir.
Benim blogum, doğası gereği hız ve performans odaklı olan Hugo mimarisi üzerine kurulu. Hugo tarafından statik site olarak üretilen bu hafiflik, insan ziyaretçiler için gayet iyi bir deneyim sunuyor. Peki, bir yapay zeka ajanının gözünden tarandığında durum ne? Bir LLM veya AI Agent bloguma geldiğinde ne görüyor?
Cloudflare Servisi: Siten AI Ajanlarına Hazır Mı?
Cloudflare, yapay zeka ajanlarının web sitelerini nasıl gördüğünü analiz etmek için özel bir servis sunuyor. Is Your Site Agent-Ready? adlı bu araç, sitenizin AI ajanları tarafından ne kadar iyi taranabildiğini ve anlaşıldığını gösteren kapsamlı bir rapor sağlıyor. Ben de bu servisi kullanarak blogumun yapay zeka ajanlarına ne kadar hazır olduğunu test ettim.
İtiraf edeyim sonuçlar pek iç açıcı değildi. Cloudflare’ın raporuna göre, blogumun AI ajanları tarafından taranması ve anlaşılması konusunda ciddi eksiklikler vardı. İşte bu yazı ile bu eksiklikleri nasıl giderdiğimi, hangi optimizasyonları yaptığımı ve blogumun yapay zeka ajanlarına karşı nasıl daha “agent-ready” hale geldiğini adım adım paylaşacağım.
Haydi Başlayalım…
Haydi öncelikle blogumu bir test edelim. Ancak burada önemli bir nokta var, o da “Customize scan (Taramayı özelleştir)” diyerek Site Type (Site Türü)’nü doğru olarak seçmek.
Benim blogum bir Content Site yani bir içerik sitesi. Ben tüm kontrolleri yapacak olursam API Catalog, OAuth, UCP gibi şeyler de bekleyecektir ki pek anlamlı talepler değil şu an için.
Blogumun adresini yazdım ve “Tara” (Scan) butonuna bastım. Site tipini seçmeden ilk yaptığımda skor sadece 8’di. Açıkçası skoru görünce “Yok artık!” demekten kendimi alamadım.
robots.txt
Blogumun robots.txt dosyası yokmuş, bazen böyle temel şeyleri gözden kaçırıyoruz. O nedenle bu tür testler bu tür temel gözden kaçırmalardan kurtulmak için de iyi bir yol oluyor.
Hugo’da hugo.toml içine enableRobotsTXT = true yazdığında otomatik olarak en basit haliyle bir robots.txt dosyası oluşturuluyor.
Site tipini doğru seçip robots.txt’i de ekleyince skor 33’e çıktı.

Ancak bu en basit hali blogumun Agent Ready skorunu sadece 9 puan artırdı ve şu hataları ortadan kaldırmadı:
- “No AI-specific bot rules and no wildcard rules in robots.txt”: Yapay zeka ajanları için özel kurallar yok ve robots.txt’de joker karakter (*) içeren kurallar yok. Bu, AI ajanlarının hangi sayfalara erişebileceği konusunda belirsizlik yaratır.
- “No Content Signals found in robots.txt”: robots.txt dosyasında AI ajanlarına yönelik içerik sinyalleri bulunmuyor. Bu, ajanların hangi tür içeriğin önemli olduğunu anlamasını zorlaştırır.
Ben de hugo.toml dosyasındaki enableRobotsTXT değerini false olarak değiştirdim ve static/robots.txt dosyasını ekleyerek şu şekilde özelleştirdim.
Ve bu özelleştirmeler skorumu tam 33 puan yükselterek skoru 66’ya çıkarmayı başardı. Çok basit bir değişiklikle skoru epey bir yükseltmiş olmak keyfimi epey yerine getirdi.
User-agent: *
Allow: /
# AI Content Usage Preferences
Content-Signal: ai-train=no
Content-Signal: search=yes
Content-Signal: ai-input=yes
# Sitemap
Sitemap: https://www.okck.net/sitemap.xml
Link Headers
Yapay zeka botları ve otonom ajanlar, bir web sitesinin mimarisini ve içeriğini anlamak için HTML dokümanını bütünüyle ayrıştırmak yerine öncelikle HTTP yanıtlarındaki Link başlıklarını (RFC 8288) kontrol eder. Cloudflare’in “Is It Agent Ready?” testlerinde karşılaşılan “eksik veya geçersiz Link başlığı” uyarıları tam olarak bu semantik optimizasyonun eksikliğinden kaynaklanır.
İlk etapta blogumun HTTP yanıtlarında sitemap.xml dosyasına işaret eden bir Link başlığı bulunmuyordu. Ancak arama motorları için de facto bir standart olan rel=“sitemap” ifadesi tek başına yeterli değildir; çünkü bu ilişki türü IANA (Internet Assigned Numbers Authority) resmi kayıtlarında otonom ajanlar veya veri keşfi için tanımlanmış bir “ilişki türü” (relation type) olarak geçmez. Cloudflare testlerini başarıyla geçmek ve LLM tarayıcılarına anlamlı bir veri sunmak için, sitenin makine okunabilir doğal çıktısı olan RSS feed’ini de IANA onaylı rel=“alternate” ilişkisiyle bu başlığa dahil etmek gerekir.
Netlify üzerinde barındırdığım Hugo blogumda bu optimizasyonu gerçekleştirmek için netlify.toml dosyasına aşağıdaki yapılandırmayı eklemek bir seçenekti:
[[headers]]
for = "/"
[headers.values]
# Retaining sitemap for traditional search engines
# Using IANA-registered 'alternate' for structured RSS data
Link = '</sitemap.xml>; rel="sitemap", </index.xml>; rel="alternate"; type="application/rss+xml"'
Ancak, altyapı bağımsızlığını korumak ve doğrudan Netlify konfigürasyon dosyalarına bağımlı kalmamak adına daha taşınabilir bir yöntem seçtim ve static/_headers dosyasına şu şekilde bir ekleme yaptım:
/
# Retaining sitemap for traditional search engines
# Using IANA-registered 'alternate' for structured RSS data
Link: </sitemap.xml>; rel="sitemap", </index.xml>; rel="alternate"; type="application/rss+xml"
Hugo, derleme (build) esnasında static klasöründeki bu dosyayı doğrudan kök dizine (public/) taşır ve Netlify bu standart dosyayı otomatik olarak tanıyarak HTTP header kurallarını sunucu seviyesinde uygular.
Bu optimizasyonu uyguladıktan sonra Cloudflare testlerindeki Link header eksikliği uyarısı ortadan kalktı ve blogumun yapay zeka ajanları tarafından daha iyi taranabilir hale gelmesini sağladı. Bu tür semantik optimizasyonlar, sadece insan ziyaretçiler için değil, aynı zamanda internetin yeni otonom kullanıcıları için de sitenizin erişilebilirliğini ve görünürlüğünü artırır.
Bu değişiklikten sonra blogumun AI ajanlarına karşı ne kadar hazır olduğunu tekrar test ettim ve skorum 17 puan daha artarak 83’e çıktı.

Markdown Protokolü (Markdown Negotiation)
Blogum için son optimizasyon parametresi ise Markdown Protokolü oldu. Ben Cloudflare DNS’lerini kullandığım için Cloudflare Workers ile çözdüm ama Hugo üzerinde yapmak da mümkün ama yapılandırma noktasında emek isteyen bir konu.
Özel Çıktı Formatı Tanımlamak
Öncelikle hugo.toml dosyanızda tarayıcıların ve ajanların tanıyacağı özel bir MIME türü ve çıktı formatı (Output Format) kurgulamanız gerekir:
[mediaTypes."text/markdown"]
suffixes = ["md"]
[outputFormats.CustomMarkdown]
mediaType = "text/markdown"
baseName = "index"
isHTML = false
fromLayout = true
Tüm Sayfa Tipleri İçin Çıktı İzinlerini Ayarlamak
Ardından Hugo’ya ana sayfa, tekil sayfalar, kategoriler ve etiketler için hem HTML hem de bu yeni Markdown formatını üretmesini söylemelisiniz:
[outputs]
home = ["HTML", "CustomMarkdown"]
page = ["HTML", "CustomMarkdown"]
section = ["HTML", "CustomMarkdown"]
Her Sayfa Şablonu İçin .md Layout’u Tasarlamak
İşin en zahmetli kısmı burası. Hugo varsayılan olarak .html şablonlarını kullanır. Markdown çıktısı üretebilmek için layouts/ klasörünüzün altındaki her mimari için (örneğin layouts/_default/single.html yanına layouts/_default/single.md) ayrı birer şablon dosyası oluşturmalısınız. Bu şablonların içine de HTML etiketleri barındırmayan, tamamen ham Markdown kodları üreten template fonksiyonları yazmanız gerekir.
Hosting Tarafında İçerik Protokolü Ayarlamak
Hugo tüm bu süreçlerin sonunda her yazınız için index.html dosyasının yanına bir de index.md üretecektir. Ancak iş burada bitmiyor. Gelen ajan okck.net/hugo-blog adresine Accept: text/markdown başlığıyla istek attığında, sunucunun (Netlify) onu otomatik olarak okck.net/hugo-blog/index.md dosyasına yönlendirmesi gerekir. Bunu da Netlify tarafında karmaşık _redirects kurallarıyla ya da .htaccess üzerindeki karmaşık kodlarla çözmeniz gerekir.
Markdown Protokolünü Cloudflare Workers ile Uygulamak
Ben bu yönlendirme işlemini Cloudflare Workers ile çözdüm. Cloudflare Workers, gelen HTTP isteklerini yakalayarak özel mantıkla yönlendirme yapmanıza olanak tanır. Ben de gelen istekte Accept: text/markdown başlığıyla gelen ajan isteklerini yakalayan ve HTML içeriği anında temiz bir Markdown mimarisine dönüştüren hafif bir Worker kuralı ile bu sorunu kökten çözebiliyoruz.
Cloudflare Worker Oluşturma
- Cloudflare Dashboard’a giriş yapın.
- Sol menüden “Workers & Pages” bölümüne gidin ve “Create Application” butonuna tıklayın.
- “Create Worker” seçeneğini seçin, Worker’a bir isim verin (örn: hugo-markdown-negotiation) ve Deploy edin.
- Worker oluştuktan sonra “Edit Code” butonuna tıklayın ve içerideki tüm kodu silip aşağıdaki optimize edilmiş JavaScript kodunu yapıştırın:
export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
const acceptHeader = request.headers.get("Accept") || "";
const isMarkdownRequest =
acceptHeader.includes("text/markdown") &&
response.headers.get("Content-Type")?.includes("text/html");
if (!isMarkdownRequest) return response;
const html = await response.text();
const markdown = htmlToMarkdown(html);
// Preserve all original headers, only override what's necessary
const newHeaders = new Headers(response.headers);
newHeaders.set("Content-Type", "text/markdown; charset=utf-8");
newHeaders.set("X-Markdown-Source", "cloudflare-worker");
if (!newHeaders.has("Cache-Control")) {
newHeaders.set("Cache-Control", "public, max-age=14400");
}
return new Response(markdown, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
},
};
function htmlToMarkdown(html) {
let md = html;
// 1. Remove noise blocks before any other processing
md = md.replace(/<script[\s\S]*?<\/script>/gi, "");
md = md.replace(/<style[\s\S]*?<\/style>/gi, "");
md = md.replace(/<nav[\s\S]*?<\/nav>/gi, "");
md = md.replace(/<footer[\s\S]*?<\/footer>/gi, "");
md = md.replace(/<header[\s\S]*?<\/header>/gi, "");
md = md.replace(/<aside[\s\S]*?<\/aside>/gi, "");
md = md.replace(/<figure[\s\S]*?<\/figure>/gi, "");
// 2. Fenced code blocks — must run before inline code to avoid double-backtick corruption
md = md.replace(
/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi,
(_, code) => "```\n" + decodeEntities(code.trim()) + "\n```\n\n"
);
// 3. Inline code
md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, (_, code) => "`" + decodeEntities(code) + "`");
// 4. Blockquotes
md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_, inner) => {
const text = stripTags(inner).trim();
return text.split("\n").map(line => "> " + line.trim()).join("\n") + "\n\n";
});
// 5. Headings h1–h4 collapsed into a single pass
md = md.replace(/<h([1-4])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, t) =>
"#".repeat(Number(level)) + " " + stripTags(t).trim() + "\n\n"
);
// 6. Paragraphs
md = md.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, t) => stripTags(t).trim() + "\n\n");
// 7. Ordered lists — convert li items with incrementing counters before stripping ol wrapper
md = md.replace(/<ol[^>]*>([\s\S]*?)<\/ol>/gi, (_, inner) => {
let i = 0;
return inner.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_, item) => {
i++;
return `${i}. ${stripTags(item).trim()}\n`;
}) + "\n";
});
// 8. Unordered lists
md = md.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_, inner) =>
inner.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_, item) => `- ${stripTags(item).trim()}\n`) + "\n"
);
// 9. Inline formatting — bold and italic each collapsed into a single pass
md = md.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, "**$2**");
md = md.replace(/<(em|i)[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");
// 10. Images (before links, since img can appear inside anchor tags)
md = md.replace(/<img[^>]+alt=["']([^"']*)["'][^>]+src=["']([^"']+)["'][^>]*/gi, "");
md = md.replace(/<img[^>]+src=["']([^"']+)["'][^>]*/gi, "");
// 11. Links
md = md.replace(/<a[^>]+href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, "[$2]($1)");
// 12. Horizontal rules
md = md.replace(/<hr[^>]*>/gi, "\n---\n\n");
// 13. Strip all remaining tags
md = stripTags(md);
// 14. Decode HTML entities
md = decodeEntities(md);
// 15. Normalize excessive blank lines
md = md.replace(/\n{3,}/g, "\n\n").trim();
return md;
}
function stripTags(html) {
return html.replace(/<[^>]+>/g, "");
}
function decodeEntities(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/ /g, " ")
.replace(/—/g, "—")
.replace(/–/g, "–")
.replace(/…/g, "…");
}
- Kodunuzu kaydedin ve Deploy edin.
Worker’ı Sitenin Alan Adına Bağlama
Yazdığımız bu kuralın blogda çalışabilmesi için rotasını (Route) belirlemeliyiz:
- Cloudflare Dashboard’da sitenizi seçin (okck.net).
- Sol menüden “Websites” altından sitenize tıklayın, ardından “Workers Routes” sekmesine gidin.
- “Add Route” butonuna tıklayın.
- Ayarları şu şekilde doldurun:
- Route: www.okck.net/* (ve eğer non-www kullanıyorsanız okck.net/* için de ikinci bir kural ekleyebilirsiniz).
- Worker: Az önce oluşturduğunuz Worker’ı seçin (hugo-markdown-negotiation).
- “Save” diyerek kaydedin.
Ve Son Skor…
Bu optimizasyonu uyguladıktan sonra blogumun AI ajanlarına karşı ne kadar hazır olduğunu tekrar test ettim ve skorum 17 puan daha artarak 100’e çıktı. Artık blogum, yapay zeka ajanları tarafından tam anlamıyla taranabilir, anlaşılabilir ve kullanılabilir hale gelmiş oldu.

Özetle
Blogumun yapay zeka ajanlarına karşı ne kadar hazır olduğunu test etmek için Cloudflare’ın “Is It Agent Ready?” servisini kullandım. İlk testte skor sadece 8’di, ancak robots.txt ekleyip doğru bir şekilde yapılandırarak, Link header’ları optimize ederek ve Markdown Protokolü uygulayarak skoru 100’e çıkarmayı başardım. Bu süreç, blogumun sadece insan ziyaretçiler için değil, aynı zamanda internetin yeni otonom kullanıcıları için de erişilebilir ve kullanılabilir hale gelmesini sağladı.
