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-results adresine 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_SLASHES flag’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_UNICODE ve JSON_UNESCAPED_SLASHES flag’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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir