WordPress’te BreadcrumbList Schema Ekleme: functions.php ile
Google’ın arama sonuçlarında sitenizin breadcrumb yolunu görmek istiyorsunuz ama eklenti kurmaktan kaçınıyorsunuz. Haklısınız da, her şey için eklenti kurmak WordPress sitenizi gereksiz yere şişirir. functions.php dosyasına birkaç fonksiyon ekleyerek BreadcrumbList schema markup’ını kendiniz yönetebilirsiniz. Bu yazıda hem basit hem de ileri seviye senaryoları ele alacağız.
BreadcrumbList Schema Nedir ve Neden Önemlidir
BreadcrumbList schema, Google’a sayfanızın site hiyerarşisindeki konumunu anlatan yapısal veridir. JSON-LD formatında sayfanızın bölümüne eklenir ve arama sonuçlarında URL yerine okunabilir breadcrumb yolu gösterilmesini sağlar.
Örneğin https://siteniz.com › Elektronik › Telefonlar › iPhone 15 şeklinde görünen o güzel hiyerarşik URL gösterimi, doğrudan bu schema’dan geliyor. CTR (tıklama oranı) üzerinde ciddi etkisi var.
Google’ın schema kurallarına göre breadcrumb listesi şu şekilde çalışır:
- @context: Schema.org URL’si
- @type: BreadcrumbList tanımı
- itemListElement: Breadcrumb öğelerinin dizisi
- item: Her bir breadcrumb öğesinin URL’si
- name: Görünen metin
- position: Sıra numarası (1’den başlar)
Temel Kurulum: functions.php’ye İlk Fonksiyonu Eklemek
Önce en basit haliyle başlayalım. Bu fonksiyon, tekli sayfalara (single post, page) temel breadcrumb schema ekler.
function get_breadcrumb_schema() {
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => array()
);
$position = 1;
// Ana sayfa her zaman ilk eleman
$schema['itemListElement'][] = array(
'@type' => 'ListItem',
'position' => $position,
'item' => array(
'@id' => home_url('/'),
'name' => get_bloginfo('name')
)
);
if ( is_singular() ) {
$position++;
$schema['itemListElement'][] = array(
'@type' => 'ListItem',
'position' => $position,
'item' => array(
'@id' => get_permalink(),
'name' => get_the_title()
)
);
}
return json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT );
}
function output_breadcrumb_schema() {
echo '<script type="application/ld+json">' . get_breadcrumb_schema() . '</script>';
}
add_action( 'wp_head', 'output_breadcrumb_schema' );
Bu temel sürüm çalışır ama kategori hiyerarşisini, sayfa ebeveynlerini veya WooCommerce ürün sayfalarını desteklemez. Biraz daha ilerleyelim.
Kategori Hiyerarşisini Dahil Etmek
Gerçek dünyada blog yazıları kategoriler altında bulunur. Bir yazı “Teknoloji > Mobil > Android” kategorisindeyse bu hiyerarşinin schema’ya yansıması gerekiyor.
function get_post_category_breadcrumbs( $post_id ) {
$items = array();
$cats = get_the_category( $post_id );
if ( empty( $cats ) ) {
return $items;
}
// En derin kategoriyi bul (child kategori)
$deepest_cat = $cats[0];
foreach ( $cats as $cat ) {
if ( $cat->parent != 0 ) {
$deepest_cat = $cat;
break;
}
}
// Kategori zincirini ters çevirerek oluştur
$cat_chain = array();
$current = $deepest_cat;
while ( $current ) {
$cat_chain[] = $current;
if ( $current->parent ) {
$current = get_term( $current->parent, 'category' );
} else {
break;
}
}
// Ters çevir: üst kategoriden alta doğru sırala
$cat_chain = array_reverse( $cat_chain );
foreach ( $cat_chain as $cat ) {
$items[] = array(
'url' => get_category_link( $cat->term_id ),
'name' => $cat->name
);
}
return $items;
}
Bu fonksiyonu ana schema fonksiyonuyla birleştireceğiz. Kategori zincirini ters sıralamak önemli bir detay, yoksa Google hata raporu verebiliyor.
Gelişmiş Ana Fonksiyon: Tüm Sayfa Tiplerini Desteklemek
Şimdi asıl işi yapan fonksiyonu yazalım. Bu versiyon; tekil yazıları, kategorileri, sayfaları, etiketleri ve arama sonuçlarını kapsar.
function build_breadcrumb_schema_data() {
$items = array();
$position = 1;
// 1. Adım: Ana sayfa her zaman başta
$items[] = array(
'position' => $position,
'url' => home_url('/'),
'name' => get_bloginfo('name')
);
// 2. Adım: Sayfa tipine göre devam
if ( is_front_page() ) {
// Ana sayfa - sadece tek eleman yeterli
return $items;
}
if ( is_singular( 'post' ) ) {
$cats = get_post_category_breadcrumbs( get_the_ID() );
foreach ( $cats as $cat ) {
$position++;
$items[] = array(
'position' => $position,
'url' => $cat['url'],
'name' => $cat['name']
);
}
$position++;
$items[] = array(
'position' => $position,
'url' => get_permalink(),
'name' => get_the_title()
);
} elseif ( is_page() ) {
// Üst sayfaları kontrol et
$ancestors = array_reverse( get_post_ancestors( get_the_ID() ) );
foreach ( $ancestors as $ancestor_id ) {
$position++;
$items[] = array(
'position' => $position,
'url' => get_permalink( $ancestor_id ),
'name' => get_the_title( $ancestor_id )
);
}
$position++;
$items[] = array(
'position' => $position,
'url' => get_permalink(),
'name' => get_the_title()
);
} elseif ( is_category() ) {
$current_cat = get_queried_object();
if ( $current_cat->parent ) {
$parent = get_term( $current_cat->parent, 'category' );
$position++;
$items[] = array(
'position' => $position,
'url' => get_category_link( $parent->term_id ),
'name' => $parent->name
);
}
$position++;
$items[] = array(
'position' => $position,
'url' => get_category_link( $current_cat->term_id ),
'name' => $current_cat->name
);
} elseif ( is_tag() ) {
$tag = get_queried_object();
$position++;
$items[] = array(
'position' => $position,
'url' => get_tag_link( $tag->term_id ),
'name' => $tag->name
);
} elseif ( is_search() ) {
$position++;
$items[] = array(
'position' => $position,
'url' => get_search_link( get_search_query() ),
'name' => '"' . get_search_query() . '" için arama sonuçları'
);
} elseif ( is_404() ) {
$position++;
$items[] = array(
'position' => $position,
'url' => home_url('/404'),
'name' => 'Sayfa Bulunamadı'
);
}
return $items;
}
WooCommerce Ürün Sayfaları için Özel Destek
Eğer WooCommerce kullanıyorsanız ürün sayfaları, ürün kategorileri ve mağaza sayfası için ayrı mantık gerekiyor.
function add_woocommerce_breadcrumb_items( &$items, &$position ) {
if ( ! function_exists('is_woocommerce') ) {
return;
}
if ( is_shop() ) {
$shop_page_id = wc_get_page_id('shop');
$position++;
$items[] = array(
'position' => $position,
'url' => get_permalink( $shop_page_id ),
'name' => get_the_title( $shop_page_id )
);
} elseif ( is_product_category() ) {
$shop_page_id = wc_get_page_id('shop');
$position++;
$items[] = array(
'position' => $position,
'url' => get_permalink( $shop_page_id ),
'name' => get_the_title( $shop_page_id )
);
$current_term = get_queried_object();
if ( $current_term->parent ) {
$parent_term = get_term( $current_term->parent, 'product_cat' );
$position++;
$items[] = array(
'position' => $position,
'url' => get_term_link( $parent_term ),
'name' => $parent_term->name
);
}
$position++;
$items[] = array(
'position' => $position,
'url' => get_term_link( $current_term ),
'name' => $current_term->name
);
} elseif ( is_product() ) {
$shop_page_id = wc_get_page_id('shop');
$position++;
$items[] = array(
'position' => $position,
'url' => get_permalink( $shop_page_id ),
'name' => get_the_title( $shop_page_id )
);
// Ürün kategorisini breadcrumb'a ekle
$product_cats = wc_get_product_terms( get_the_ID(), 'product_cat', array('orderby' => 'parent') );
if ( ! empty( $product_cats ) ) {
$main_cat = end( $product_cats );
if ( $main_cat->parent ) {
$parent_cat = get_term( $main_cat->parent, 'product_cat' );
$position++;
$items[] = array(
'position' => $position,
'url' => get_term_link( $parent_cat ),
'name' => $parent_cat->name
);
}
$position++;
$items[] = array(
'position' => $position,
'url' => get_term_link( $main_cat ),
'name' => $main_cat->name
);
}
$position++;
$items[] = array(
'position' => $position,
'url' => get_permalink(),
'name' => get_the_title()
);
}
}
Her Şeyi Bir Araya Getiren Çıktı Fonksiyonu
Şimdi tüm parçaları birleştiren ve wp_head hook’una bağlanan nihai fonksiyonu yazalım.
function output_full_breadcrumb_schema() {
// Sadece gerçek sayfalar için çalış
if ( is_admin() || is_feed() ) {
return;
}
$items = array();
$position = 1;
// Temel öğeleri al
if ( function_exists('is_woocommerce') && ( is_shop() || is_product_category() || is_product() ) ) {
// Ana sayfa
$items[] = array(
'position' => $position,
'url' => home_url('/'),
'name' => get_bloginfo('name')
);
// WooCommerce öğeleri
add_woocommerce_breadcrumb_items( $items, $position );
} else {
$items = build_breadcrumb_schema_data();
}
if ( empty( $items ) || count( $items ) < 2 ) {
return; // Tek elemanlı schema gerekmiyor
}
// Schema yapısını oluştur
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => array()
);
foreach ( $items as $item ) {
$schema['itemListElement'][] = array(
'@type' => 'ListItem',
'position' => (int) $item['position'],
'item' => array(
'@id' => esc_url( $item['url'] ),
'name' => esc_html( $item['name'] )
)
);
}
$json = json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT );
if ( $json ) {
echo "n" . '<script type="application/ld+json">' . "n";
echo $json;
echo "n" . '</script>' . "n";
}
}
add_action( 'wp_head', 'output_full_breadcrumb_schema', 5 );
Hook önceliğini 5 olarak verdik çünkü diğer schema eklentileriyle çakışma yaşamamak için erken yüklenmesini istiyoruz.
Custom Post Type Desteği Eklemek
Sitenizde “etkinlik”, “portföy” veya “film” gibi özel post type’lar varsa onlar için de destek eklemek gerekiyor.
function add_custom_post_type_breadcrumbs( $post_type, $taxonomy = '' ) {
// Bu fonksiyon build_breadcrumb_schema_data() içinde çağrılır
$items = array();
$position = 2; // Ana sayfa position 1'de
// Post type arşiv sayfasını ekle
$archive_url = get_post_type_archive_link( $post_type );
$post_type_obj = get_post_type_object( $post_type );
if ( $archive_url && $post_type_obj ) {
$items[] = array(
'position' => $position,
'url' => $archive_url,
'name' => $post_type_obj->labels->name
);
$position++;
}
// Taxonomy terimi varsa ekle
if ( $taxonomy ) {
$terms = get_the_terms( get_the_ID(), $taxonomy );
if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
$term = $terms[0];
$items[] = array(
'position' => $position,
'url' => get_term_link( $term ),
'name' => $term->name
);
$position++;
}
}
// Tekil sayfa
$items[] = array(
'position' => $position,
'url' => get_permalink(),
'name' => get_the_title()
);
return $items;
}
// Kullanım örneği - functions.php içinde bu filtreyi tanımlayın
add_filter( 'custom_breadcrumb_items', function( $items, $post_type ) {
if ( $post_type === 'etkinlik' ) {
return add_custom_post_type_breadcrumbs( 'etkinlik', 'etkinlik_kategori' );
}
return $items;
}, 10, 2 );
Schema’yı Test Etmek
Kodu ekledikten sonra test etmek için birkaç yöntem var:
- Google Rich Results Test:
https://search.google.com/test/rich-resultsadresine sayfanızın URL’sini girin, schema’nın doğru parse edildiğini görün. - Schema Markup Validator:
https://validator.schema.org/adresini kullanın. - Google Search Console: “Geliştirmeler > Breadcrumb” bölümünden hata ve uyarıları takip edin.
Sayfa kaynağında doğrulama yapmak için tarayıcıda Ctrl+U ile kaynak kodunu açıp BreadcrumbList araması yapabilirsiniz. JSON bloğunu görmüyorsanız fonksiyon çalışmıyor demektir.
Sık karşılaşılan sorunlar:
- Position hatası: Position değerleri integer olmalı, string kabul etmiyor.
(int)cast kullandığımızdan bu zaten çözülü. - Duplicate schema: Başka bir eklenti de BreadcrumbList ekliyorsa Google uyarı verebilir. Yoast veya RankMath kullanıyorsanız onların breadcrumb özelliğini kapatın.
- URL encoding sorunu:
JSON_UNESCAPED_SLASHESflag’i kullanmazsanız URL’lerdeki slash karakterleri escape edilir.
Önbellek ile Performans Optimizasyonu
Yüksek trafikli sitelerde her sayfa yüklemesinde bu hesaplamalar yapılmasını istemeyebilirsiniz.
function get_cached_breadcrumb_schema() {
$cache_key = 'breadcrumb_schema_' . get_queried_object_id() . '_' . get_query_var('paged');
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
// Schema'yı oluştur (kısaltılmış örnek)
ob_start();
output_full_breadcrumb_schema();
$schema_output = ob_get_clean();
// 12 saat önbelleğe al
set_transient( $cache_key, $schema_output, 12 * HOUR_IN_SECONDS );
return $schema_output;
}
// Yazı güncellendiğinde önbelleği temizle
function clear_breadcrumb_schema_cache( $post_id ) {
$cache_key = 'breadcrumb_schema_' . $post_id . '_0';
delete_transient( $cache_key );
}
add_action( 'save_post', 'clear_breadcrumb_schema_cache' );
add_action( 'edited_term', function( $term_id ) {
// Kategori düzenlendiğinde ilgili önbellekleri temizlemek için
// daha kapsamlı bir temizlik gerekebilir
wp_cache_flush_group('breadcrumb_schemas');
});
Transient kullanımı özellikle shared hosting ortamlarında fark yarattığı için bu adımı atlamayın.
Gerçek Dünya Senaryoları
Senaryo 1: Müşterinin e-ticaret sitesinde Yoast kullanıyordu ve breadcrumb schema hep boş geliyordu. Nedeni: ürün sayfaları WooCommerce template’i kullandığı için Yoast’un hook’u çalışmıyordu. Yukarıdaki WooCommerce özel fonksiyonu ekleyince sorun çözüldü.
Senaryo 2: Haber sitesinde yazılar birden fazla kategoriye atanıyordu. Bu durumda en derin kategorinin en doğru breadcrumb’ı verdiğini tespit ettik. get_post_category_breadcrumbs fonksiyonundaki parent != 0 kontrolü tam bu iş için yazıldı.
Senaryo 3: Çok dilli WooCommerce sitesinde (WPML) her dil için farklı URL’ler üretiliyordu. home_url('/') zaten WPML’i desteklediği için sorun yaşamadık ama get_bloginfo('name') her dil için ayrı kontrol gerektirdi.
Sonuç
functions.php üzerinden BreadcrumbList schema eklemek düşündüğünüzden çok daha pratik ve kontrol sizde kalıyor. Eklenti bağımlılığı yaratmıyor, sitenizin tam yapısına göre özelleştirebiliyorsunuz ve performans üzerinde tam hakimiyetiniz var.
Önemli noktaları özetleyelim:
- Her zaman
JSON_UNESCAPED_UNICODEveJSON_UNESCAPED_SLASHESflag’lerini kullanın - Position değerlerini mutlaka integer cast edin
- WooCommerce varsa ayrı mantık yazın, genel fonksiyona karıştırmayın
- Yoast veya RankMath varsa çakışmayı önlemek için onların breadcrumb özelliğini kapatın
- Google Search Console’u düzenli takip edin, hataları erkenden yakalayın
Kodu production’a almadan önce Google Rich Results Test’ten geçirmeyi unutmayın. Birkaç günde bir Search Console’a bakarak schema’nın düzgün indekslendiğini doğrulayın. SEO sonuçları hemen gelmez ama birkaç hafta içinde arama sonuçlarında breadcrumb görünümünü fark edeceksiniz.
