WordPress’te Breadcrumb Navigasyonu Ekleme

Bir WordPress sitesinde ziyaretçiler nerede olduklarını bilmek ister. Özellikle büyük e-ticaret siteleri veya blog platformlarında kullanıcılar kategori hiyerarşisinde kaybolabiliyor. İşte bu noktada breadcrumb navigasyonu devreye giriyor. “Ekmek kırıntısı” olarak da bilinen bu navigasyon sistemi, kullanıcıya “Anasayfa > Blog > WordPress > Bu Yazı” gibi bir yol haritası sunuyor. Bu yazıda WordPress’te breadcrumb navigasyonunu sıfırdan functions.php dosyası üzerinden nasıl ekleyeceğimizi, özelleştireceğimizi ve production ortamında nasıl kullanacağımızı ele alacağız.

Breadcrumb Nedir ve Neden Önemli?

Breadcrumb navigasyonu hem kullanıcı deneyimi hem de SEO açısından kritik öneme sahip. Google, structured data ile işaretlenmiş breadcrumb’ları arama sonuçlarında doğrudan gösteriyor. Bu da tıklanma oranını (CTR) artırıyor.

Bir senaryo düşünelim: WooCommerce ile çalışan bir mağazanız var. Müşteri “Elektronik > Telefon > iPhone 15 Pro” sayfasındaysa ve geri dönmek istiyorsa, tarayıcının geri tuşuna basmak yerine doğrudan “Telefon” kategorisine tıklaması çok daha verimli. Bu tür senaryolarda breadcrumb hayat kurtarıyor.

Temel Breadcrumb Fonksiyonu

İlk olarak en basit halini yazalım. Bu fonksiyon functions.php dosyasına ekleniyor ve tema şablon dosyalarından çağrılıyor.

// functions.php dosyasina ekle
function custom_breadcrumb() {
    // Ana sayfa kontrolu
    if ( is_front_page() ) {
        return;
    }

    $separator = ' › ';
    $output    = '<nav class="breadcrumb" aria-label="Breadcrumb"><ol>';
    $output   .= '<li><a href="' . home_url() . '">Anasayfa</a></li>';

    if ( is_singular( 'post' ) ) {
        $categories = get_the_category();
        if ( ! empty( $categories ) ) {
            $cat     = $categories[0];
            $output .= '<li>' . $separator . '<a href="' . get_category_link( $cat->term_id ) . '">' . esc_html( $cat->name ) . '</a></li>';
        }
        $output .= '<li>' . $separator . '<span>' . get_the_title() . '</span></li>';

    } elseif ( is_category() ) {
        $output .= '<li>' . $separator . '<span>' . single_cat_title( '', false ) . '</span></li>';

    } elseif ( is_page() ) {
        global $post;
        if ( $post->post_parent ) {
            $parent_id = $post->post_parent;
            $output   .= '<li>' . $separator . '<a href="' . get_permalink( $parent_id ) . '">' . get_the_title( $parent_id ) . '</a></li>';
        }
        $output .= '<li>' . $separator . '<span>' . get_the_title() . '</span></li>';
    }

    $output .= '</ol></nav>';
    echo $output;
}

Bu temel fonksiyon bloglar için yeterli ama production ortamında birkaç ek kontrole ihtiyaç duyuyor.

Fonksiyonu Tema Şablonuna Eklemek

Fonksiyonu yazdıktan sonra bunu tema dosyalarından çağırmamız gerekiyor. Genellikle header.php dosyasının altına ya da single.php, page.php gibi şablon dosyalarının başına eklenir.

// single.php veya page.php icinde kullanim
<?php
if ( function_exists( 'custom_breadcrumb' ) ) {
    custom_breadcrumb();
}
?>

Eğer tema child theme üzerinden yönetiliyorsa (ki önerim bu yönde), bu çağrıyı child theme’in şablon dosyalarına eklemelisin. Direkt parent theme dosyalarını düzenlemek tema güncellemelerinde tüm değişiklikleri siliyor, bunu yaşayan her sysadmin bilir ne kadar sinir bozucu olduğunu.

WooCommerce ile Entegrasyon

WooCommerce kullanan bir sitede ürün sayfaları, kategori sayfaları ve tag sayfaları için ayrı mantık yazmak gerekiyor. Aşağıdaki genişletilmiş versiyon bunu ele alıyor:

function custom_breadcrumb_woocommerce() {
    if ( is_front_page() ) {
        return;
    }

    $separator = ' <span class="sep">/</span> ';
    $output    = '<nav class="breadcrumb woo-breadcrumb" aria-label="Breadcrumb"><ol itemscope itemtype="https://schema.org/BreadcrumbList">';

    // Anasayfa
    $output .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
    $output .= '<a itemprop="item" href="' . home_url() . '"><span itemprop="name">Anasayfa</span></a>';
    $output .= '<meta itemprop="position" content="1" /></li>';

    $position = 2;

    // WooCommerce dukkan sayfasi
    if ( is_shop() ) {
        $output .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
        $output .= '<span itemprop="name">Magaza</span>';
        $output .= '<meta itemprop="position" content="' . $position . '" /></li>';

    // WooCommerce urun kategorisi
    } elseif ( is_product_category() ) {
        $output .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
        $output .= '<a itemprop="item" href="' . get_permalink( wc_get_page_id( 'shop' ) ) . '"><span itemprop="name">Magaza</span></a>';
        $output .= '<meta itemprop="position" content="' . $position . '" /></li>';
        $position++;

        $current_term = get_queried_object();
        if ( $current_term->parent ) {
            $parent_term = get_term( $current_term->parent, 'product_cat' );
            $output     .= $separator;
            $output     .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
            $output     .= '<a itemprop="item" href="' . get_term_link( $parent_term ) . '"><span itemprop="name">' . esc_html( $parent_term->name ) . '</span></a>';
            $output     .= '<meta itemprop="position" content="' . $position . '" /></li>';
            $position++;
        }

        $output .= $separator;
        $output .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
        $output .= '<span itemprop="name">' . esc_html( $current_term->name ) . '</span>';
        $output .= '<meta itemprop="position" content="' . $position . '" /></li>';

    // Tekil urun sayfasi
    } elseif ( is_product() ) {
        $output .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
        $output .= '<a itemprop="item" href="' . get_permalink( wc_get_page_id( 'shop' ) ) . '"><span itemprop="name">Magaza</span></a>';
        $output .= '<meta itemprop="position" content="' . $position . '" /></li>';
        $position++;

        $terms = get_the_terms( get_the_ID(), 'product_cat' );
        if ( $terms && ! is_wp_error( $terms ) ) {
            $term    = array_shift( $terms );
            $output .= $separator;
            $output .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
            $output .= '<a itemprop="item" href="' . get_term_link( $term ) . '"><span itemprop="name">' . esc_html( $term->name ) . '</span></a>';
            $output .= '<meta itemprop="position" content="' . $position . '" /></li>';
            $position++;
        }

        $output .= $separator;
        $output .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
        $output .= '<span itemprop="name">' . get_the_title() . '</span>';
        $output .= '<meta itemprop="position" content="' . $position . '" /></li>';
    }

    $output .= '</ol></nav>';
    echo $output;
}

Bu versiyonda Schema.org BreadcrumbList markup’ı da var. Google bu markup’ı anlıyor ve arama sonuçlarında zengin snippet olarak gösteriyor.

CSS ile Breadcrumb Stilini Ayarlamak

Fonksiyon HTML çıktısı üretiyor ama görsel stil için CSS gerekiyor. Bunu child theme’in style.css dosyasına ya da özelleştirici üzerinden ekleyebilirsin.

/* breadcrumb stilleri */
.breadcrumb {
    padding: 10px 0;
    margin-bottom: 20px;
    font-size: 14px;
    color: #666;
}

.breadcrumb ol {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
}

.breadcrumb li {
    display: flex;
    align-items: center;
}

.breadcrumb a {
    color: #0073aa;
    text-decoration: none;
    transition: color 0.2s ease;
}

.breadcrumb a:hover {
    color: #005177;
    text-decoration: underline;
}

.breadcrumb .sep {
    margin: 0 8px;
    color: #999;
}

.breadcrumb span[itemprop="name"] {
    color: #333;
    font-weight: 500;
}

/* Mobil uyumluluk */
@media ( max-width: 768px ) {
    .breadcrumb {
        font-size: 12px;
    }

    .breadcrumb ol {
        flex-wrap: wrap;
    }
}

Özel Post Type (CPT) Desteği

Eğer sitende özel post type’lar varsa (portföy, etkinlik, proje gibi), bunlar için de breadcrumb mantığı yazman gerekiyor. Örneğin bir “projeler” custom post type’ı için:

function breadcrumb_custom_post_type_support( $output, $position, $separator ) {
    $post_type        = get_post_type();
    $post_type_object = get_post_type_object( $post_type );

    if ( ! $post_type_object ) {
        return $output;
    }

    // Varsa arsiv sayfasi linki
    if ( get_post_type_archive_link( $post_type ) ) {
        $output  .= $separator;
        $output  .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
        $output  .= '<a itemprop="item" href="' . get_post_type_archive_link( $post_type ) . '">';
        $output  .= '<span itemprop="name">' . esc_html( $post_type_object->labels->name ) . '</span></a>';
        $output  .= '<meta itemprop="position" content="' . $position . '" /></li>';
        $position++;
    }

    // Iliskili taksonomi varsa
    $taxonomies = get_object_taxonomies( $post_type, 'objects' );
    foreach ( $taxonomies as $taxonomy ) {
        if ( $taxonomy->hierarchical ) {
            $terms = get_the_terms( get_the_ID(), $taxonomy->name );
            if ( $terms && ! is_wp_error( $terms ) ) {
                $term    = array_shift( $terms );
                $output .= $separator;
                $output .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
                $output .= '<a itemprop="item" href="' . get_term_link( $term ) . '">';
                $output .= '<span itemprop="name">' . esc_html( $term->name ) . '</span></a>';
                $output .= '<meta itemprop="position" content="' . $position . '" /></li>';
                $position++;
            }
            break;
        }
    }

    // Sayfa basligi
    $output .= $separator;
    $output .= '<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">';
    $output .= '<span itemprop="name">' . get_the_title() . '</span>';
    $output .= '<meta itemprop="position" content="' . $position . '" /></li>';

    return $output;
}

Bu fonksiyonu ana breadcrumb fonksiyonunun içinde is_singular() kontrolüne entegre edebilirsin.

Shortcode Olarak Kullanmak

Bazen Gutenberg bloğu içinden ya da sayfa derleyiciden (Elementor, Divi) breadcrumb eklemek gerekiyor. Bu durumda shortcode tanımlamak işe yarıyor:

// Shortcode kaydi
function register_breadcrumb_shortcode() {
    add_shortcode( 'my_breadcrumb', function( $atts ) {
        $atts = shortcode_atts(
            array(
                'separator' => '/',
                'home_text' => 'Anasayfa',
                'show_home' => 'yes',
            ),
            $atts,
            'my_breadcrumb'
        );

        if ( is_front_page() ) {
            return '';
        }

        ob_start();
        // functions.php'deki ana fonksiyonu cagir
        custom_breadcrumb();
        return ob_get_clean();
    } );
}
add_action( 'init', 'register_breadcrumb_shortcode' );

Artık içerik editörlerinde [my_breadcrumb] yazarak breadcrumb eklenebiliyor. Özellikle sayfa derleyici kullanan projelerde bu yaklaşım çok işe yarıyor çünkü her şablon dosyasını ayrı ayrı düzenleme ihtiyacını ortadan kaldırıyor.

Archive, Tag ve Arama Sayfaları

Blog tabanlı sitelerde kategori arşivleri, tag sayfaları ve arama sonuçları da breadcrumb gerektiriyor. Bu sayfaları kapsamlı bir fonksiyonla ele alalım:

function extended_breadcrumb_pages( $output, $position, $separator ) {
    // Kategori arsivi
    if ( is_category() ) {
        $category = get_queried_object();
        if ( $category->parent != 0 ) {
            $parent_category = get_term( $category->parent, 'category' );
            $output         .= $separator;
            $output         .= '<li><a href="' . get_category_link( $parent_category->term_id ) . '">';
            $output         .= esc_html( $parent_category->name ) . '</a></li>';
            $position++;
        }
        $output .= $separator;
        $output .= '<li><span>' . esc_html( $category->name ) . '</span></li>';

    // Etiket sayfasi
    } elseif ( is_tag() ) {
        $output .= $separator;
        $output .= '<li><span>Etiket: ' . single_tag_title( '', false ) . '</span></li>';

    // Yazar arsivi
    } elseif ( is_author() ) {
        $output .= $separator;
        $output .= '<li><span>Yazar: ' . get_the_author() . '</span></li>';

    // Tarih arsivi
    } elseif ( is_date() ) {
        if ( is_year() ) {
            $output .= $separator;
            $output .= '<li><span>' . get_the_date( 'Y' ) . '</span></li>';
        } elseif ( is_month() ) {
            $output .= $separator;
            $output .= '<li><a href="' . get_year_link( get_the_date( 'Y' ) ) . '">' . get_the_date( 'Y' ) . '</a></li>';
            $output .= $separator;
            $output .= '<li><span>' . get_the_date( 'F' ) . '</span></li>';
        }

    // Arama sonuclari
    } elseif ( is_search() ) {
        $output .= $separator;
        $output .= '<li><span>Arama: "' . esc_html( get_search_query() ) . '"</span></li>';

    // 404 sayfasi
    } elseif ( is_404() ) {
        $output .= $separator;
        $output .= '<li><span>Sayfa Bulunamadi</span></li>';
    }

    return $output;
}

Performans Optimizasyonu: Object Caching

Büyük sitelerde her sayfa yüklemesinde breadcrumb hesaplamak CPU’yu yorabiliyor. Özellikle binlerce ürünü olan WooCommerce mağazalarında transient API ile sonuçları cache’leyebilirsin:

function cached_breadcrumb() {
    if ( is_admin() ) {
        return;
    }

    $post_id    = get_the_ID();
    $cache_key  = 'breadcrumb_' . $post_id . '_' . get_locale();

    $cached = get_transient( $cache_key );
    if ( false !== $cached ) {
        echo $cached;
        return;
    }

    ob_start();
    custom_breadcrumb(); // Ana breadcrumb fonksiyonunu cagir
    $output = ob_get_clean();

    // 12 saat cache'le
    set_transient( $cache_key, $output, 12 * HOUR_IN_SECONDS );

    // Sayfa guncellendiginde cache'i temizle
    add_action( 'save_post', function( $saved_post_id ) use ( $post_id, $cache_key ) {
        if ( $saved_post_id === $post_id ) {
            delete_transient( $cache_key );
        }
    } );

    echo $output;
}

Bu yaklaşım özellikle Redis ya da Memcached kullanan sunucularda etkin çalışıyor. Cache’i WordPress dashboard’dan içerik güncellendiğinde otomatik temizleme sayesinde eski breadcrumb gösterme riski de ortadan kalkıyor.

Gerçek Dünya Senaryosu: Haber Sitesi

Bir haber sitesi yönetiyorsun ve şu yapı var:

  • Anasayfa
  • Kategori: Teknoloji
  • Alt Kategori: Yapay Zeka
  • Haber: “ChatGPT’nin Yeni Modeli Tanıtıldı”

Bu hiyerarşi için breadcrumb şöyle görünmeli: Anasayfa > Teknoloji > Yapay Zeka > ChatGPT'nin Yeni Modeli Tanıtıldı

Çok seviyeli kategori desteği için şu fonksiyon işe yarıyor:

function get_category_hierarchy( $category_id ) {
    $hierarchy = array();
    $category  = get_term( $category_id, 'category' );

    while ( $category && ! is_wp_error( $category ) ) {
        array_unshift( $hierarchy, $category );
        if ( $category->parent == 0 ) {
            break;
        }
        $category = get_term( $category->parent, 'category' );
    }

    return $hierarchy;
}

// Ana breadcrumb fonksiyonunda kullanim
function news_site_breadcrumb() {
    if ( is_front_page() ) {
        return;
    }

    $output = '<nav class="breadcrumb" aria-label="Breadcrumb"><ol>';
    $output .= '<li><a href="' . home_url() . '">Anasayfa</a></li>';

    if ( is_singular( 'post' ) ) {
        $categories = get_the_category();
        if ( ! empty( $categories ) ) {
            $hierarchy = get_category_hierarchy( $categories[0]->term_id );
            foreach ( $hierarchy as $cat ) {
                $output .= ' <span>/</span> ';
                $output .= '<li><a href="' . get_category_link( $cat->term_id ) . '">';
                $output .= esc_html( $cat->name ) . '</a></li>';
            }
        }
        $output .= ' <span>/</span> ';
        $output .= '<li><span>' . get_the_title() . '</span></li>';
    }

    $output .= '</ol></nav>';
    echo $output;
}

Bu yapıda ne kadar derin bir kategori hiyerarşisi olursa olsun, tüm seviyeleri otomatik olarak listeleniyor.

Yaygın Hatalar ve Çözümleri

Production’da karşılaşılan tipik sorunlar:

  • Yanlış kategori gösterimi: Bir yazının birden fazla kategorisi varsa PHP’nin array_shift() ile ilk kategoriyi alması her zaman doğru olmayabilir. Birincil kategori belirlemek için Yoast SEO’nun _yoast_wpseo_primary_term meta alanını kullanabilirsin.
  • Sayfa önbelleği çakışması: Nginx FastCGI cache ya da WP Rocket gibi eklentiler breadcrumb’u cache’lediğinde hatalı gösterim olabiliyor. Bu durumda cache’leme katmanını doğru yapılandırmak gerekiyor.
  • Özel karakter sorunu: Türkçe kategori isimlerinde esc_html() kullanmadan doğrudan çıktı almak XSS açığına yol açıyor. Her zaman esc_html() ya da esc_attr() kullan.
  • WooCommerce breadcrumb çakışması: WooCommerce kendi breadcrumb fonksiyonunu otomatik ekliyor. Özel breadcrumb ile çakışmaması için WooCommerce’in varsayılan breadcrumb’unu kaldırman gerekiyor:
// WooCommerce varsayilan breadcrumb'u kaldir
remove_action( 'woocommerce_before_main_content', 'woocommerce_breadcrumb', 20 );

Bu satırı functions.php dosyasına ekledikten sonra kendi breadcrumb fonksiyonunu WooCommerce şablonlarına eklemelisin.

JSON-LD Alternatifi

HTML içi Schema markup yerine bölümüne JSON-LD formatında breadcrumb verisi enjekte etmek de mümkün ve tercih edilen bir yöntem:

function breadcrumb_json_ld() {
    if ( is_front_page() || is_404() ) {
        return;
    }

    $items      = array();
    $position   = 1;
    $items[]    = array(
        '@type'    => 'ListItem',
        'position' => $position,
        'name'     => 'Anasayfa',
        'item'     => home_url(),
    );

    if ( is_singular( 'post' ) ) {
        $position++;
        $categories = get_the_category();
        if ( ! empty( $categories ) ) {
            $items[] = array(
                '@type'    => 'ListItem',
                'position' => $position,
                'name'     => esc_html( $categories[0]->name ),
                'item'     => get_category_link( $categories[0]->term_id ),
            );
            $position++;
        }
        $items[] = array(
            '@type'    => 'ListItem',
            'position' => $position,
            'name'     => get_the_title(),
            'item'     => get_permalink(),
        );
    }

    if ( empty( $items ) ) {
        return;
    }

    $schema = array(
        '@context'        => 'https://schema.org',
        '@type'           => 'BreadcrumbList',
        'itemListElement' => $items,
    );

    echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_UNICODE ) . '</script>';
}
add_action( 'wp_head', 'breadcrumb_json_ld' );

Bu yöntem hem HTML’i temiz tutuyor hem de Google Search Console’da daha az sorun yaratıyor.

Sonuç

WordPress’te breadcrumb navigasyonu eklemek aslında göründüğü kadar karmaşık değil, ama doğru yapılması için birkaç kritik noktaya dikkat etmek şart. Structured data ile işaretlenmiş bir breadcrumb hem kullanıcı deneyimini iyileştiriyor hem de SEO’ya doğrudan katkı sağlıyor. WooCommerce gibi kapsamlı eklentilerle çalışırken varsayılan breadcrumb ile çakışmaları önlemek, cache katmanını doğru yapılandırmak ve Türkçe karakter sorunlarına dikkat etmek önemli.

functions.php üzerinden yapılan bu özelleştirmeler child theme ile kullanılmalı, plugin güncellemeleri ya da tema güncellemeleri sırasında değişikliklerin kaybolmaması için mutlaka version control (Git) altında tutulmalı. Production ortamında herhangi bir değişikliği önce staging ortamında test et, sonra canlıya al. Bu basit kural çok fazla gece mesaisini önlüyor.

Bir yanıt yazın

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