Mert Özen Her Satırda Daha İleri
SEO ve GEO SEO Temelleri — #04

Sonuç Sayfasında Görünmek: Title Link, Snippet, Meta Description ve Site Name

Mert Özen 8 Kas 2026 9 dk 2 görüntülenme
Sonuç Sayfasında Görünmek: Title Link, Snippet, Meta Description ve Site Name

Google sonuçtaki başlığı yalnızca title'dan değil h1, og:title ve link metinlerinden de üretir; snippet'i çoğunlukla sayfa içeriğinden alır ve meta description için uzunluk sınırı yoktur. Razor ve Angular 22'de sayfa bazlı title ve meta'yı gerçek sunucu çıktısıyla, snippet kontrollerini, site name ve favicon kurallarını ve mertozen.com'daki title son ekini inceliyoruz.

Sürüm notu: Bu yazı Google'ın title link, snippet, site name ve favicon dokümanlarının Kasım 2026 hâlini esas alır; kod örnekleri .NET 10 ve Angular 22.2 ile derlenip denenmiştir.

Önce Tahmin Et

Bookstore'un kitap sayfasında <title> şu: Async in Practice | Bookstore. Meta description ise kitabın tek cümlelik özeti. Google'da bir okur "async cancellation backpressure" diye arıyor ve sonuçta sayfan çıkıyor. Soru: sonuçtaki mavi başlık birebir senin <title>'ın mı olur? Altındaki gri metin her zaman meta description mı? Ve meta description'ı 155 karakterde kesmezsen Google sayfayı cezalandırır mı?

Google sonuçtaki tıklanabilir başlığa title link diyor ve onu otomatik üretiyor. Dokümana göre kullandığı kaynaklar şunlar: <title> element'inin içeriği, sayfada görünen ana başlık, <h1> gibi heading element'leri, og:title meta tag'i, stil ile büyük ve belirgin yapılmış diğer metinler, sayfadaki diğer metinler, sayfadaki anchor metinleri, sayfaya işaret eden link'lerin metinleri ve WebSite structured data'sı. Yani <title> en güçlü aday, ama tek aday değil.

Google title'ı ne zaman değiştirir? Dokümandaki örnekler bir geliştiricinin kendi koduyla ürettiği hatalara çok benziyor:

  • Yarı boş title: başlığın bir parçası eksik, örneğin şablon değişkeni boş geldiği için yalnızca | Bookstore kalmış.
  • Eskimiş title: tekrarlanan sayfada title eski tarihi taşıyor, görünen başlık yeni tarihi.
  • Gerçeği yansıtmayan title: <title> içerikle örtüşmüyor.
  • Micro-boilerplate: birçok sayfa aynı title'ı paylaşıyor; Google ayırt edici bir ayrıntıyı sayfadan bulup ekleyebiliyor.
  • Net bir ana başlık yok: birden çok büyük başlık eşit önemde görünüyor; Google ilkini kullanabiliyor.
  • Dil veya yazı sistemi uyuşmazlığı: title'ın dili sayfanın ana içeriğinin dilinden farklı.

Google'ın önerileri de buna göre: her sayfaya açıklayıcı ve kısa bir <title> ver, "Home" gibi belirsiz etiketlerden ve keyword stuffing'den kaçın, her title'ı ayırt edici yap, site adını başa veya sona tire, iki nokta ya da dik çizgi gibi bir ayraçla ekle, ana başlığı sayfanın en belirgin metni yap. Title için bir uzunluk sınırı yok; title link'ler gerektiğinde, genellikle cihaz genişliğine sığacak şekilde kesiliyor. Title link'i elle değiştirmenin bir yolu da yok: sayfayı düzeltirsin, Google yeniden crawl edip işler; bu birkaç günden birkaç haftaya kadar sürebilir.

Razor'da Sayfa Bazlı Title ve Description

ASP.NET Core Razor'da en sade yol, her sayfanın ViewData'ya kendi değerlerini yazması ve layout'un bunları tek yerde basması. Bookstore'un layout'u:

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] | Bookstore</title>
    @if (ViewData["Description"] is string description)
    {
        <meta name="description" content="@description" />
    }
    @if (ViewData["Canonical"] is string canonical)
    {
        <link rel="canonical" href="@canonical" />
    }
    @await RenderSectionAsync("Head", required: false)
</head>
@page "/books/{slug}"
@model Bookstore.Web.Pages.Books.DetailModel
@{
    ViewData["Title"] = Model.Book.Title;
    ViewData["Description"] = Model.Book.Summary;
    ViewData["Canonical"] = $"https://bookstore.example/books/{Model.Book.Slug}";
}

Gerçek çıktı:

$ curl -s http://localhost:5481/books/async-in-practice | grep -E "<title>|description|canonical"
    <title>Async in Practice | Bookstore</title>
        <meta name="description" content="Real-world async/await in .NET, from cancellation to backpressure." />
        <link rel="canonical" href="https://bookstore.example/books/async-in-practice" />

İki küçük karar var. Description yoksa tag'i hiç basmıyoruz; boş bir content="" Google'a bir şey söylemez. Site adı ise layout'ta sabit: sayfalar yalnızca kendi başlığını verir, böylece "yarı boş title" ancak sayfa başlığı boş gelirse oluşur. Bunu engellemek için Title boşsa build'i ya da testi kıran bir kontrol eklemek ucuz bir sigorta.

Angular'da Route Bazlı Title ve Meta

Angular'da aynı işi Router'ın title özelliği, bir TitleStrategy ve Meta service'i yapıyor. Site adını tek yerde eklemek için strategy:

import { Injectable, inject } from '@angular/core';
import { Meta, Title } from '@angular/platform-browser';
import { RouterStateSnapshot, TitleStrategy } from '@angular/router';

@Injectable({ providedIn: 'root' })
export class BookstoreTitleStrategy extends TitleStrategy {
  private readonly title = inject(Title);

  override updateTitle(snapshot: RouterStateSnapshot): void {
    const pageTitle = this.buildTitle(snapshot);
    this.title.setTitle(pageTitle ? `${pageTitle} | Bookstore` : 'Bookstore');
  }
}

@Injectable({ providedIn: 'root' })
export class PageMeta {
  private readonly meta = inject(Meta);

  setDescription(description: string): void {
    this.meta.updateTag({ name: 'description', content: description });
  }
}
const bookTitle: ResolveFn<string> = route => books[route.paramMap.get('slug')!]?.title ?? 'Book not found';

@Component({
  selector: 'app-book-page',
  template: `<h1>{{ book?.title }}</h1><p>{{ book?.summary }}</p>`,
})
export class BookPage implements OnInit {
  private readonly route = inject(ActivatedRoute);
  private readonly pageMeta = inject(PageMeta);

  readonly book = books[this.route.snapshot.paramMap.get('slug')!];

  ngOnInit(): void {
    this.pageMeta.setDescription(this.book?.summary ?? 'Books for developers.');
  }
}

export const routes: Routes = [
  { path: 'books/:slug', component: BookPage, title: bookTitle },
];

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    { provide: TitleStrategy, useClass: BookstoreTitleStrategy },
  ],
};

Bu kodun önemli olan tarafı tarayıcıda değil sunucuda görünüyor. @angular/platform-server'ın renderApplication fonksiyonuyla iki URL'yi (Uniform Resource Locator) sunucuda render edip head'deki satırları süzdüm:

$ node dist/render.js /books/async-in-practice
Angular is running in development mode.
<title>Async in Practice | Bookstore</title>
<meta name="description" content="Real-world async/await in .NET, from cancellation to backpressure.">
<h1>Async in Practice</h1>

$ node dist/render.js /books/unknown-book
Angular is running in development mode.
<title>Book not found | Bookstore</title>
<meta name="description" content="Books for developers.">
<h1></h1>

İlk URL tam istediğimiz gibi. İkincisi bir uyarı: bulunamayan kitap için de title ve description üretiliyor ve status code'u ayrıca ayarlamadığın sürece bu sayfa 200 döner. Google'ın gözünde bu bir soft 404 adayı. Gerçek 404 döndürmek için Angular SSR'da (Server-Side Rendering) response status'u ayarlamak gerekiyor; bunu ve RenderMode seçimini Teknik SEO (Search Engine Optimization) serisinin "Angular SSR ile SEO" yazısı anlatıyor. Uygulama yalnızca CSR (Client-Side Rendering) ile çalışıyorsa title ve description ilk HTML'de (HyperText Markup Language) yoktur; Google bunları ancak render aşamasında görür.

Snippet ve Meta Description

Snippet, title link'in altındaki açıklama. Google onu otomatik ve ağırlıklı olarak sayfanın içeriğinden üretiyor; amaç, kullanıcının aramasına en iyi uyan kısmı önizlemek. Bu yüzden aynı sayfa farklı sorgularda farklı snippet'lerle görünebilir. Google meta description'ı ise sayfanın içeriğinden "daha doğru bir açıklama" verdiği durumlarda kullanıyor.

Baştaki üçüncü sorunun cevabı: meta description için bir uzunluk sınırı yok, ceza da yok. Snippet'ler gerektiğinde cihaz genişliğine göre kesiliyor. Google'ın önerdiği şey uzunluk değil, içerik: her sayfaya özgü bir açıklama yaz, yazar, yayın tarihi, fiyat gibi faydalı bilgileri ekle, keyword listesi değil okunabilir bir özet yaz. Ana sayfa ve liste sayfaları için site seviyesinde açıklama kullanmak makul.

mertozen.com'un kaynak kodunda ilginç bir ayrıntı var: blog yazısının description'ı, özetin ilk 157 karakteri alınıp sonuna "…" eklenerek üretiliyor. Bu bir ihlal değil, ama Google'ın dokümanında böyle bir kesme gerekmiyor ve kesim kelimenin ortasına denk gelebiliyor. Özet zaten kısa ve cevap niteliğinde yazılıyorsa, tamamını vermek ve kesmeyi Google'a bırakmak daha temiz bir seçenek.

Snippet Kontrolleri: nosnippet, max-snippet ve data-nosnippet

Snippet'te neyin görünmeyeceğini sen belirleyebilirsin. nosnippet robots kuralı sayfa için metin snippet'ini ve video önizlemesini kapatır. max-snippet:[number] snippet'i en fazla o kadar karaktere sınırlar; 0 nosnippet ile aynı, -1 uzunluğu Google'a bırakır. Sayfanın yalnızca bir kısmını gizlemek için data-nosnippet attribute'u var ve yalnızca span, div ve section element'lerinde çalışıyor:

<section id="stock">
    <div data-nosnippet>Only 2 copies left, offer ends Friday.</div>
</section>

Bookstore'un stok ve kampanya metni hızla eskiyor; bir hafta sonra snippet'te görünmesini istemiyoruz. Bir not: Google'ın robots meta tag dokümanına göre nosnippet ve max-snippet artık Google'ın AI (Artificial Intelligence, yapay zekâ) özellikleri olan AI Overviews ve AI Mode için de geçerli; içeriğin bu özelliklere doğrudan girdi olmasını engelliyor ya da sınırlıyor. AI özellikleriyle ilgili kontrolleri GEO (Generative Engine Optimization) serisinde ayrıntılı ele alacağız.

Nisan 2026'da snippet dokümanına "read more" deep link'leri için bir bölüm eklendi: Google'ın snippet'ten sayfanın ilgili bölümüne götüren link göstermesini kolaylaştırmak için içeriği açılır panellerin ve sekmelerin arkasına saklamamak, sayfa yüklenirken scroll konumunu JavaScript ile zorlamamak ve yükleme sırasında URL'deki hash fragment'ı silmemek öneriliyor.

Site Name ve Favicon

Sonuçta title link'in üstünde site adı ve favicon görünür. Google site adını ana sayfadan ve web'deki referanslardan otomatik üretiyor; tercih ettiğin adı bildirmenin en güçlü yolu ana sayfadaki WebSite structured data'sının name property'si, alternateName ise tercih sırasına göre yedekler. Google ayrıca og:site_name'e, <title>'a ve ana sayfadaki başlıklara da bakıyor. Site adı domain ve subdomain seviyesinde destekleniyor; subdirectory'ler için desteklenmiyor. Yani /en/ altındaki İngilizce bölüm kendi site adını alamaz. Markup'ın ayrıntısı ilerideki Structured Data ve Schema.org serisinde.

Favicon için kurallar kısa: kare (1:1) ve en az 8x8 piksel, ama çeşitli yüzeylerde iyi görünmesi için 48x48'den büyük olması öneriliyor; rel="icon" tercih edilen değer. Hostname başına tek favicon var: subdomain'ler kendi favicon'unu alabilir, subdirectory'ler domain'inkini paylaşır. Favicon URL'sini sık değiştirme; yeniden crawl birkaç günden birkaç haftaya sürebilir. Google Ağustos 2026'da desteklenen favicon dosya formatlarını dokümanda açıkça listeledi; desteğin kendisi değişmedi.

mertozen.com'da " — Mert Özen" Son Eki

Bu sitenin her sayfa title'ı aynı son ekle bitiyor: blog yazısında {post title} — Mert Özen. Google'ın önerisi site adını başa veya sona bir ayraçla eklemek; örnekleri tire, iki nokta ve dik çizgi. Uzun tire örnek listede yok, ama doküman bir ayraç "örneği" veriyor, kesin bir liste değil. Daha önemli ayrıntı şu: Google, domain seviyesindeki site adını sonuçta zaten gösteriyorsa, title link'te tekrar eden site adını atabileceğini yazıyor. Yani son ek büyük ihtimalle okurun gördüğü başlığı uzatmıyor, ama karar Google'ın.

TR ve EN title'ların ayrı yazılması da doğru bir karar. Kaynak kodda başlık CMS'ten (Content Management System) dile göre geliyor: TR sayfada Türkçe, /en/ altındaki sayfada İngilizce başlık. Google'ın listesindeki "dil veya yazı sistemi uyuşmazlığı" tam olarak İngilizce bir sayfada Türkçe title bırakıldığında oluşan durum.

Üretimde Dikkat

Title ve description'ı şablonda test et. Bir integration test bütün route'ları gezip boş veya yinelenen <title>'ları yakalayabilir. Micro-boilerplate'in en sık nedeni, sayfalama veya filtre sayfalarının ana listeyle aynı title'ı taşıması.

SSR veya prerender kullanıyorsan head'i sunucu çıktısında kontrol et, tarayıcıda değil. Tarayıcının sekme başlığı JavaScript çalıştıktan sonraki durumu gösterir; Google'ın ilk HTML'de gördüğünü değil.

Title link değişikliklerini beklemeyi öğren. Google'ın notuna göre yeniden işleme günler, bazen haftalar sürer; bir günde ölçüp geri almak yanlış sonuca götürür.

Sık Düşülen Tuzaklar

Title'ı keyword listesine çevirmek. "Async, Await, C#, .NET, Async Book" gibi bir title hem keyword stuffing'e yaklaşır hem de Google'ın değiştirme olasılığını artırır.

Meta description'ı karakter sayarak yazmak. Sınır yok; asıl soru, açıklamanın sayfayı içerikten daha doğru anlatıp anlatmadığı.

Her sayfaya aynı description'ı koymak. Google bu durumda description'ı kullanmak için bir neden bulamaz ve içerikten snippet üretir.

data-nosnippet'i yanlış element'te kullanmak. p veya custom element üzerinde çalışmaz; içeriği bir div, span veya section ile sar.

Küçük Bir Deneme

★☆☆ Kendi sitende beş sayfanın <title>'ını ve meta description'ını curl ile çek. Aralarında aynı olan var mı?

★★☆ Razor layout'a, ViewData["Title"] boş geldiğinde development ortamında exception fırlatan, production'da site adını tek başına basan bir kontrol ekle.

★★★ Angular örneğinde bulunamayan kitap için TitleStrategy'nin "Book not found" yerine boş bir title bırakmasını sağla, sonra bunun neden "yarı boş title" sorununa dönüştüğünü sunucu çıktısıyla göster ve doğru çözümü (404) uygula.

Sonuç sayfasında nasıl göründüğümüzü artık kontrol edebiliyoruz. Bir sonraki yazıda görünüp görünmediğimizi ölçmeye başlıyoruz: Google Search Console ve Bing Webmaster Tools kurulumu, URL Inspection ve temel raporlar.