Cevap Veren Sayfa Yazmak: Başlık Yapısı, İlk Paragraf, Kod Örnekleri, Görseller ve İki Dilli İçerik
Cevap veren bir sayfa, başlıktaki sorunun cevabını ilk paragrafta verir, tek ve net bir h1 taşır, kodu metin olarak ve gerçek çıktısıyla gösterir; heading sırası Google için sıralama faktörü değil ama okur ve erişilebilirlik için önemli. Başlık yapısını, ilk paragrafı, teknik içerikte sürüm ve çıktıyı, görsellerde alt text ve dosya adını, TR/EN yerelleştirmeyi ve AI destekli yazımı anlatıyor, .NET 10 ve AngleSharp ile bir sayfa denetleyicisi yazıyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10 ve AngleSharp 1.10.3 ile derlenip denenmiştir; Google'ın başlangıç rehberinin (SEO Starter Guide), title link, görsel ve generative AI içerik dokümanlarının Kasım 2026 hâlini esas alır.
Önce Tahmin Et
Bookstore blog'undaki "Kitap Önerileri" sayfasının gövdesi şöyle başlıyor:
<h1>Book Picks</h1>
<h1>Our favourite books of the year</h1>
<p>Every year our team gets together at the end of December, and after a long discussion over coffee, and a few disagreements about whether a book about databases can ever be called exciting, we finally agree on a list that we think is worth sharing with everyone who reads this blog, so here it is, and we hope you enjoy reading it as much as we enjoyed writing it.</p>
<h4>Domain-Driven Design</h4>
<img src="/img/image1.png">
<h2>Code sample</h2>
<img src="/img/code-screenshot.png" alt="code">
Bir ekip arkadaşın "h2'den h4'e atladık, iki tane h1 var; Google bunun için sayfayı cezalandırır" diyor. Bir başkası "ilk paragraf samimi, okur sever" diyor. Sence Google heading sırasını önemsiyor mu? Bu sayfada okurun ve Google'ın asıl sorunu ne: heading'ler mi, ilk paragraf mı, görseller mi?
Başlık Yapısı: H1 ve Heading Hiyerarşisi
İlk iddiayı Google'ın kendi cümlesiyle cevaplayalım. SEO Starter Guide, heading'lerin anlamsal sırada olmasının ekran okuyucu kullanan okurlar için çok faydalı olduğunu söylüyor, ama Google Search açısından durumu açık bırakmıyor: "it doesn't matter if you're using them out of order" (Google, SEO Starter Guide). Yani h2'den h4'e atlamak bir sıralama cezası getirmiyor. İdeal bir heading sayısı da yok.
Bu, heading yapısının önemsiz olduğu anlamına gelmiyor. Heading'ler okurun sayfayı taramasının ana aracı: bir geliştirici bir hata mesajıyla geldiğinde önce başlıklara bakar, ilgili bölüme atlar. Ekran okuyucu kullanıcıları da sayfada heading'ler arasında gezinir; seviye atlamak bu kullanıcılar için sayfanın yapısını bozar. Accessibility tarafını bu turda yazılan Accessibility (a11y) serisi ayrıntılı ele alıyor.
Ana başlık ise ayrı bir konu, çünkü Google'ın title link'i seçerken baktığı yerlerden biri. Title link dokümanı, ana başlığın sayfadaki en belirgin metin olmasını, örneğin görünen ilk h1 olmasını öneriyor; aynı görsel ağırlıkta birden fazla başlık varsa Google hangisinin ana başlık olduğundan emin olamayabiliyor. Örnek sayfadaki iki h1 tam olarak bu belirsizliği yaratıyor. Title link'in nasıl seçildiğini SEO Temelleri serisinin "Sonuç Sayfasında Görünmek" yazısında ayrıntılı anlattık.
Teknik blog için pratik kural basit: tek bir h1, sayfanın sorusunu ya da konusunu taşısın; h2'ler okurun sorabileceği alt sorular olsun; h3'ler bir h2'nin içindeki adımlar. Heading'i metni büyütmek için değil, yapıyı anlatmak için kullan.
İlk Paragraf ve Taranabilir Yapı
Örnek sayfanın asıl sorunu ilk paragraf. 69 kelimelik bir cümle, okura sayfanın ne anlattığını söylemiyor; kitap listesi hâlâ görünmüyor. Kitap önerisi arayan okur bu paragrafı atlar, bir hata mesajıyla gelen okur ise sayfayı kapatır. Cevap veren bir sayfada ilk paragraf, başlıktaki sorunun cevabını bir iki cümleyle verir; ayrıntı, hikâye ve gerekçe sonra gelir.
Bu kural, AI cevap motorlarının alıntılaması açısından da konuşuluyor. Araştırmaların bu konuda gerçekte ne gösterdiğini, hangi iddiaların abartıldığını GEO (AI cevap motorları için içerik optimizasyonu) serisinin "Alıntılanabilir İçerik: Araştırmalar Gerçekte Ne Gösteriyor?" yazısında inceledik; ama okur açısından gerekçe yeterince güçlü: cevabı arayan kişi cevabı ilk ekranda görmeli. mertozen.com'da her yazının özeti, kapak görselinin hemen altında, gövdeden önce gösteriliyor ve bu serinin kuralına göre özetin ilk cümlesi sorunun net cevabı. Yani okur sayfaya girdiği anda cevabı görüyor.
Taranabilir yapı için üç araç var ve her birinin yeri belli. Tablo karşılaştırma içindir: iki seçeneğin aynı ölçütlerle yan yana gösterilmesi gerektiğinde. Liste, sırası önemli adımlar ya da birbirinden bağımsız kısa maddeler içindir. Kod bloğu, okurun kopyalayacağı her şey içindir: komut, configuration, kod. Bunların dışında kısa paragraflar, her biri tek bir fikirle. Bir kavramı açıklayan akıcı bir metni, sırf taranabilir görünsün diye madde madde parçalamak okumayı zorlaştırır.
Teknik İçerik: Çalışan Kod, Gerçek Çıktı ve Sürüm
Teknik bir sayfanın cevap verip vermediğini okur bir şekilde test eder: kodu kopyalar ve çalıştırır. Bu yüzden teknik içerikte kalite, çalışan kod ve gerçek çıktıyla başlıyor. Serinin ilk yazısında bunu "How" sorusu, yani deneyim sinyali olarak gördük; burada sayfa yazımı açısından bakalım:
Sürüm ve tarih. Kodun hangi framework, SDK ve paket sürümüyle denendiği sayfanın başında yazmalı. mertozen.com'daki "Sürüm notu" satırı bu iş için var. Okur, kendi ortamı farklıysa bunu ilk satırda görür.
Gerçek çıktı. Derleyici uyarısı, log satırı, HTTP yanıtı ve test sonucu gerçek bir koşudan gelmeli. Okur aynı komutu çalıştırıp farklı bir çıktı görürse sayfaya güveni biter.
Kod metin olarak. Kodu ekran görüntüsü olarak koymak, okurun kopyalamasını engeller ve içeriği metin olarak okunamaz hâle getirir. Google'ın AI özellikleri sayfası da önemli bilginin metin olarak verilmesini öneriyor. Kod her zaman pre ve code içinde, metin olarak durmalı.
Bağımlılıklar. Örneğin çalışması için gereken paketler ve sürümleri, kurulum komutuyla birlikte verilmeli; "AngleSharp'ı ekle" demek yerine dotnet add package AngleSharp --version 1.10.3 yazmak.
Görseller: Alt Text, Dosya Adı ve Boyut
Google'ın görsel dokümanına göre alt metni, bir görsel için verebileceğin en önemli metadata. Aynı zamanda ekran okuyucu kullanan ve görseli yükleyemeyen okurlar için tek açıklama. İyi bir alt kısa, belirli ve görselin içerikle ilişkisini anlatan bir cümle; tek bir kelime hiç yoktan iyi ama yeterli değil, keyword doldurmak ise okuru yorar ve spam olarak değerlendirilebilir. Örnek sayfadaki alt="code" bu yüzden yetersiz.
Dosya adı da bir sinyal. Google image1.jpg gibi genel adlar yerine kısa ve açıklayıcı adlar öneriyor; görselleri yerelleştiriyorsan dosya adlarını da çevirmeni öneriyor. mertozen.com'daki figürlerin 04-content-architecture-tr.png ve -en.png biçiminde iki dilli üretilmesi bu öneriyle örtüşüyor. Görseli ilgili metnin yakınına koymak da Google'ın görselin konusunu anlamasına yardım ediyor.
Boyut tarafında width ve height attribute'ları sayfa yüklenirken yerleşimin kaymasını önlüyor; bu bir Core Web Vitals konusu ve Web Performansı serisinde ele alınıyor. Dosya boyutu da önemli: büyük görseller sayfayı yavaşlatıyor.
Sayfayı Kodla Denetlemek
Bu kontrollerin çoğu mekanik ve yayından önce otomatik yapılabilir. Önceki yazıdaki yerel Bookstore blog'unun iki sayfasını AngleSharp ile denetleyen küçük bir program yazalım: başlık ve h1, heading seviyeleri, ilk içerik paragrafının uzunluğu, görünür tarih, görsellerin alt, boyut ve dosya adları, kod bloğu sayısı ve hreflang alternatifleri.
using System.Text.RegularExpressions;
using AngleSharp;
using AngleSharp.Dom;
string[] urls = args.Length > 0 ? args :
[
"http://localhost:5281/blog/ef-core-migration-rehberi",
"http://localhost:5281/blog/kitap-onerileri-2023",
];
IBrowsingContext context = BrowsingContext.New(Configuration.Default.WithDefaultLoader());
Regex genericName = new(@"^(image|img|dsc|photo)\d*\.|screenshot", RegexOptions.IgnoreCase);
foreach (string url in urls)
{
IDocument doc = await context.OpenAsync(url);
List<string> warnings = [];
Console.WriteLine(new Uri(url).AbsolutePath);
// Başlık: tek h1 ve <title> ile uyum
IElement[] h1 = [.. doc.QuerySelectorAll("h1")];
Console.WriteLine($" title: {doc.Title}");
Console.WriteLine($" h1: {string.Join(" | ", h1.Select(h => h.TextContent))}");
if (h1.Length != 1)
{
warnings.Add($"{h1.Length} h1 elements; the main title is ambiguous");
}
// Heading hiyerarşisi: seviye atlama (h2 -> h4)
int previous = 1;
foreach (IElement h in doc.QuerySelectorAll("h1, h2, h3, h4, h5, h6"))
{
int level = h.LocalName[1] - '0';
if (level > previous + 1)
{
warnings.Add($"heading skips a level: h{previous} -> h{level} \"{h.TextContent}\"");
}
previous = level;
}
// İlk içerik paragrafı: cevap başta mı?
IElement? first = doc.QuerySelectorAll("h1 ~ p").FirstOrDefault(p => p.QuerySelector("time") is null);
int words = first?.TextContent.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length ?? 0;
Console.WriteLine($" first paragraph: {words} words");
if (words > 60)
{
warnings.Add("first paragraph is long; does it answer the question?");
}
int dates = doc.QuerySelectorAll("time[datetime]").Length;
if (dates == 0)
{
warnings.Add("no visible date (<time datetime>)");
}
foreach (IElement img in doc.QuerySelectorAll("img"))
{
string src = img.GetAttribute("src") ?? "";
string file = src[(src.LastIndexOf('/') + 1)..];
string alt = img.GetAttribute("alt") ?? "";
if (!img.HasAttribute("alt"))
{
warnings.Add($"{file}: no alt");
}
else if (alt.Split(' ').Length < 2)
{
warnings.Add($"{file}: alt \"{alt}\" is too short to describe the image");
}
if (!img.HasAttribute("width") || !img.HasAttribute("height"))
{
warnings.Add($"{file}: no width/height");
}
if (genericName.IsMatch(file))
{
warnings.Add($"{file}: file name says nothing about the content");
}
if (file.Contains("code") || alt.Contains("code"))
{
warnings.Add($"{file}: code as an image; use <pre><code>");
}
}
string[] hreflang = [.. doc.QuerySelectorAll("link[rel=alternate][hreflang]").Select(l => l.GetAttribute("hreflang")!)];
Console.WriteLine($" code blocks: {doc.QuerySelectorAll("pre > code").Length}, dates: {dates}, hreflang: {(hreflang.Length == 0 ? "-" : string.Join(",", hreflang))}");
if (!hreflang.Contains("en"))
{
warnings.Add("no EN alternate (hreflang)");
}
warnings.ForEach(w => Console.WriteLine($" WARN {w}"));
Console.WriteLine(warnings.Count == 0 ? " OK\n" : "");
}
dotnet run çıktısı:
/blog/ef-core-migration-rehberi
title: EF Core Migrations Step by Step | Bookstore Blog
h1: EF Core Migrations Step by Step
first paragraph: 36 words
code blocks: 1, dates: 2, hreflang: tr,en
OK
/blog/kitap-onerileri-2023
title: Book Picks | Bookstore Blog
h1: Book Picks | Our favourite books of the year
first paragraph: 69 words
code blocks: 0, dates: 0, hreflang: tr
WARN 2 h1 elements; the main title is ambiguous
WARN heading skips a level: h1 -> h4 "Domain-Driven Design"
WARN first paragraph is long; does it answer the question?
WARN no visible date (<time datetime>)
WARN image1.png: no alt
WARN image1.png: no width/height
WARN image1.png: file name says nothing about the content
WARN code-screenshot.png: alt "code" is too short to describe the image
WARN code-screenshot.png: no width/height
WARN code-screenshot.png: file name says nothing about the content
WARN code-screenshot.png: code as an image; use <pre><code>
WARN no EN alternate (hreflang)
Migration rehberi bütün kontrollerden geçiyor: tek h1, 36 kelimelik ve doğrudan cevap veren bir ilk paragraf, yayın ve güncelleme tarihi, metin olarak bir kod bloğu, açıklayıcı alt metniyle boyutları belli bir görsel ve TR/EN hreflang çifti. "Kitap Önerileri" sayfası ise on iki uyarı alıyor. Bunlardan hangisinin Google'ı, hangisinin okuru etkilediğini ayırmak önemli: heading seviyesindeki atlama Google'ın sıralaması için değil, okur ve erişilebilirlik için bir sorun. İki h1 title link seçimini belirsizleştiriyor. Uzun ilk paragraf ve kodun görsel olması doğrudan okuru kaybettiriyor.
Bu tür bir checker'ın sınırını da bil: "ilk paragraf 60 kelimeden uzun" kuralı bir cevabın varlığını ölçmüyor, yalnız şüpheli durumları işaretliyor. Kararı yine bir insan veriyor.
İki Dilli İçerik: Çeviri mi Yerelleştirme mi?
mertozen.com'da her yazının bir TR ve bir EN sürümü var. Bu sürümler arasındaki ilişki iki şekilde kurulabilir. Çeviri, metni cümle cümle diğer dile aktarır. Yerelleştirme ise aynı içeriği hedef okur için yeniden yazar: örnekler, terimler ve hatta başlıklar okurun arama alışkanlığına göre değişebilir.
Serinin ikinci yazısında gördüğümüz gibi TR ve EN sayfalar farklı sorgularda yarışıyor. TR sayfanın başlığı "ef core migration nasıl yapılır" sorusuna, EN sayfanınki "ef core migrations" gibi küresel bir sorguya göre seçilebilir. EN sayfanın TR'nin birebir çevirisi olması gerekmiyor; aynı konuyu, aynı kodla ama EN okurun beklediği ifadelerle anlatması yeterli. Bu sitede yazıların kodu iki dilde birebir aynı tutuluyor, yalnız kod yorumları çevriliyor; metin ise iki dilde de o dilde yazılmış gibi okunmalı.
İki sürümü bağlayan teknik parça hreflang. Her TR sayfa kendi EN karşılığını, her EN sayfa da TR karşılığını göstermeli; uygulamasını Teknik SEO serisinin hreflang yazısında yaptık. Title link dokümanının bir notu da burada önemli: title, sayfanın ana içeriğiyle aynı dilde ve yazı sisteminde olmalı. TR sayfanın title'ının İngilizce kalması, Google'ın title link'i yeniden yazmasının sebeplerinden biri.
AI Destekli Yazım
Google'ın tutumunu serinin ilk yazısında ayrıntılı gördük: AI ile üretilmiş içerik, faydalı ve özgünse sorun değil; sıralamayı manipüle etmek için toplu üretim ise scaled content abuse. Sayfa yazımı açısından asıl risk, AI'ın doğrulanmamış iddia üretmesi. Generative AI içerik rehberi, bu modellerin gerçekleri bir kaynaktan çekmek yerine olası kelime dizilerini tahmin ettiğini ve bu yüzden hatalı çıktı verebildiğini hatırlatıyor; yayından önce doğrulamayı öneriyor.
Teknik içerik için bunun üç somut karşılığı var. Uydurma API: var olmayan bir method veya parametre adı, okurun ilk derlemesinde ortaya çıkar. Uydurma kaynak: var olmayan bir doküman sayfası veya yanlış tarihli bir sürüm notu. Uydurma çıktı: hiç çalıştırılmamış bir kodun "beklenen" çıktısı. Üçüne karşı da tek savunma aynı: kodu çalıştırmak, kaynağı açıp okumak ve çıktıyı gerçek koşudan almak. Rehber bu doğrulamanın gövde metniyle sınırlı kalmamasını, title, meta description, structured data ve alt metinlerini de kapsamasını istiyor.
Üretimde Dikkat
Checker'ı yayın akışına bağla. CMS'te (içerik yönetim sistemi) bir yazı "yayına hazır" durumuna geçtiğinde önizleme URL'sine karşı bu kontrolleri çalıştırmak, iki h1'i ya da alt'sız görseli yayından önce yakalar.
Uyarıları seviyelendir. Kodun görsel olması veya ana başlığın belirsizliği yayını durdurabilir; ilk paragrafın uzunluğu ise yalnız editöre bir not olmalı.
Özet alanını zorunlu yap. Özetin ilk cümlesi sorunun cevabı olsun ve sayfada gövdeden önce görünsün; bu tek alan, ilk paragraf disiplinini bütün yazılara yayar.
Görsel adlandırma kuralı koy. Dosya adı, dil eki ve boyutlar bir kural olarak yazılıp pipeline'da kontrol edilirse, tek tek editör dikkatine kalmaz.
Sık Düşülen Tuzaklar
Giriş paragrafını hikâyeyle doldurup cevabı gizlemek. Hikâye bir yazıyı canlı kılabilir, ama cevabın yerini almamalı; önce cevap, sonra hikâye.
Heading'leri stil için kullanmak. Metni büyütmek için h4, ayırıcı olarak boş bir h2 kullanmak, sayfanın yapısını okura ve ekran okuyucuya yanlış anlatır. Görünüm CSS'in işi.
Heading sırasını SEO cezası sanmak. Google sıraya bakmıyor; ama bu, heading yapısını önemsememek için bir gerekçe değil, çünkü okur bakıyor.
EN sürümü makine çevirisiyle bırakmak. Çevirisi kontrol edilmemiş bir EN sayfa hem okuru yorar hem de aynı soruya EN okurun beklediği ifadelerle cevap vermez.
Küçük Bir Deneme
★☆☆ Kendi sitenden bir yazı seç ve yalnız ilk paragrafını oku. Başlıktaki sorunun cevabı orada mı? Değilse ilk paragrafı iki cümleyle yeniden yaz.
★★☆ PageCheck'e, title ile h1 arasında hiçbir ortak kelime yoksa uyarı veren ve html element'inin lang attribute'u ile sayfa metninin dilini karşılaştıran iki kontrol ekle.
★★★ PageCheck'i bir xUnit testine çevir: yerel Bookstore blog'unu WebApplicationFactory ile ayağa kaldırıp sitemap'teki her sayfayı denetlesin ve yayını durdurması gereken uyarılarda testi düşürsün.
Cevap veren bir sayfa yazdık. Ama sayfa yayımlandığı gün en iyi hâlinde değil; sürümler değişiyor, sorgular değişiyor. Serinin son yazısında içerik bakımına bakıyoruz: content decay'i Search Console verisiyle tespit etmek, güncelle, birleştir ya da sil kararı ve sürüm bağımlı yazıları yaşatmak.