Canonical URL Tanımlama: functions.php ile Yinelenen İçerik Önleme

SEO dünyasında en sık karşılaşılan ama en az fark edilen sorunlardan biri yinelenen içeriktir. WordPress sitende aynı içeriğe birden fazla URL üzerinden ulaşılabiliyorsa, arama motorları hangi sayfanın “asıl” sayfa olduğunu bilemez ve bu durum sıralamalarını doğrudan etkiler. Canonical URL mekanizması tam da bu noktada devreye girer. Peki WordPress’in bunu zaten yönettiğini düşünüyorsanız, yanılıyor olabilirsiniz. Yoast veya Rank Math gibi eklentiler yüzeysel çözümler sunsa da gerçek kontrolü elde etmek için functions.php dosyasına inmek gerekir.

Canonical URL Nedir ve Neden Önemlidir

Canonical URL, bir sayfanın “tercih edilen” veya “yetkili” versiyonunu arama motorlarına bildiren HTML etiketidir. bölümüne şu şekilde eklenir:

<link rel="canonical" href="https://example.com/asil-sayfa/" />

Google bu etiketi gördüğünde, farklı URL’ler üzerinden erişilen aynı içeriği tek bir kaynak altında toplar. PageRank dağılmasını önler, tarama bütçesini (crawl budget) korur ve indeksleme kalitesini artırır.

WordPress sitelerinde yinelenen içerik genellikle şu durumlarda oluşur:

  • www ve www olmayan versiyon: www.site.com ile site.com aynı sayfayı sunar
  • HTTP ve HTTPS: Yönlendirme tam yapılandırılmamışsa ikisi de erişilebilir olabilir
  • Sayfalama: /sayfa/1/ ve / aynı içeriği gösterebilir
  • WooCommerce ürün varyasyonları: ?color=red&size=L gibi parametre kombinasyonları
  • Arşiv sayfaları: Kategori, etiket, tarih arşivleri aynı yazıları listeler
  • Print versiyonlar: ?print=1 parametresi
  • Session ID’leri: Bazı cache eklentileri veya üyelik sistemleri ekler

WordPress’in Varsayılan Canonical Davranışı

WordPress 4.4 sürümünden itibaren wp_head() hook’u üzerinden otomatik canonical etiketleri ekler. Ancak bu davranış her zaman istediğiniz gibi çalışmaz. Özellikle şu senaryolarda yetersiz kalır:

  • WooCommerce filtreleme parametreleri (?min_price=100&max_price=500)
  • Özel post type’larınızda yapılandırma eksikliği
  • Sayfalama mantığında yanlış URL üretimi
  • Çoklu domain yapılarında (staging, CDN, subdomains)

Bunu test etmek için tarayıcı geliştirici araçlarında sayfa kaynağını inceleyin. içinde rel="canonical" araması yapın. Gördüğünüz URL beklediğinizle örtüşüyor mu? Çoğu zaman hayır.

functions.php’ye Neden İniyoruz

Eklenti bağımlılığını azaltmak, siteniz üzerinde tam kontrol sahibi olmak ve özelleştirilmiş iş mantığı uygulamak için functions.php ideal yerdir. Child theme kullanıyorsanız (ki kullanıyor olmalısınız), bu dosya güncellemelerden etkilenmez ve değişiklikleriniz güvende kalır.

Başlamadan önce şunu netleştirelim: Eğer Yoast SEO veya Rank Math kullanıyorsanız, bu eklentiler wp_head üzerinden canonical çıktısı üretir. Sizin kod yazdığınız fonksiyonlar çakışabilir. Ya eklentiyi bu konuda devre dışı bırakmanız gerekir ya da öncelik (priority) sıralamasını dikkatli yönetmeniz gerekir.

Temel Canonical Hook’u Anlamak

WordPress canonical URL yönetimi için iki ana hook kullanır:

  • get_canonical_url: Filtreleme hook’u, canonical URL string’ini döndürür
  • wp_head: Action hook’u, içine çıktı üretir
  • rel_canonical: Canonical etiketinin çıktısını tamamen kontrol etmenizi sağlar

Önce mevcut canonical davranışını devre dışı bırakıp kendi fonksiyonumuzu yazalım:

<?php
// Varsayılan WordPress canonical çıktısını kaldır
remove_action('wp_head', 'rel_canonical');

// Kendi canonical fonksiyonumuzu ekle
add_action('wp_head', 'custom_canonical_url', 1);

function custom_canonical_url() {
    global $wp_the_query;
    
    if (!is_singular() && !is_front_page()) {
        return;
    }
    
    $canonical = get_permalink($wp_the_query->get_queried_object_id());
    
    if ($canonical) {
        echo '<link rel="canonical" href="' . esc_url($canonical) . '" />' . "n";
    }
}

Bu temel yapıyla singular sayfalar için canonical tanımlıyoruz. Ama iş burada bitmiyor.

URL Parametrelerini Temizleme

En kritik senaryo, URL parametrelerinin canonical URL’e dahil edilmemesidir. UTM parametreleri, filtreler, session ID’leri canonical URL’e girmemelidir.

<?php
function get_clean_canonical_url() {
    global $wp;
    
    // Mevcut isteğin base URL'ini al
    $current_url = home_url(add_query_arg(array(), $wp->request));
    
    // Temizlenmesi gereken parametreler
    $ignored_params = array(
        'utm_source',
        'utm_medium', 
        'utm_campaign',
        'utm_term',
        'utm_content',
        'fbclid',
        'gclid',
        'ref',
        'source',
        'print',
        'sid',
        'session_id',
        'PHPSESSID',
    );
    
    $parsed = parse_url($current_url);
    
    if (isset($parsed['query'])) {
        parse_str($parsed['query'], $params);
        
        foreach ($ignored_params as $param) {
            unset($params[$param]);
        }
        
        $clean_query = http_build_query($params);
        $canonical = $parsed['scheme'] . '://' . $parsed['host'];
        
        if (isset($parsed['path'])) {
            $canonical .= $parsed['path'];
        }
        
        if (!empty($clean_query)) {
            $canonical .= '?' . $clean_query;
        }
    } else {
        $canonical = $current_url;
    }
    
    return trailingslashit($canonical);
}

Bu fonksiyon, UTM parametrelerini ve diğer “gürültülü” parametreleri canonical URL’den temizler. Sosyal medyadan gelen fbclid veya Google Ads’dan gelen gclid parametreleri sıklıkla sorun yaratır.

WooCommerce için Özel Canonical Yönetimi

WooCommerce sitelerinde durum çok daha karmaşıklaşır. Ürün filtreleme, sıralama parametreleri ve varyasyon URL’leri yönetilmezse canonical kargaşası kaçınılmazdır.

<?php
add_filter('get_canonical_url', 'woocommerce_canonical_fix', 10, 2);

function woocommerce_canonical_fix($canonical, $post) {
    
    if (!class_exists('WooCommerce')) {
        return $canonical;
    }
    
    // WooCommerce mağaza sayfası
    if (is_shop()) {
        $shop_page_id = wc_get_page_id('shop');
        return get_permalink($shop_page_id);
    }
    
    // Ürün kategori sayfaları - sayfalama dahil
    if (is_product_category()) {
        $term = get_queried_object();
        $base_url = get_term_link($term);
        
        // Sayfalama varsa canonical'a dahil et
        $paged = get_query_var('paged');
        if ($paged > 1) {
            return trailingslashit($base_url) . 'page/' . $paged . '/';
        }
        
        return $base_url;
    }
    
    // Tekil ürün sayfaları - varyasyon parametrelerini temizle
    if (is_product()) {
        // ?attribute_pa_renk=mavi gibi parametreleri canonical'a ekleme
        return get_permalink($post->ID);
    }
    
    return $canonical;
}

Burada dikkat edilmesi gereken nokta: WooCommerce’in kendi canonical filtresi vardır ve bunun üzerine yazıyoruz. priority değerini 10’dan yüksek tutarsanız WooCommerce’in kendi mantığı override edilecektir.

Sayfalama için Canonical Yönetimi

Sayfalama hem blog listesi hem de ürün arşiv sayfalarında önemli bir sorun kaynağıdır. İlk sayfa ile /page/1/ aynı içeriği sunar:

<?php
add_action('wp_head', 'fix_pagination_canonical', 1);

function fix_pagination_canonical() {
    
    // Sadece arşiv, kategori, etiket ve ana blog sayfalarında çalış
    if (!is_archive() && !is_home() && !is_search()) {
        return;
    }
    
    global $wp_query;
    
    $paged = get_query_var('paged');
    $current_page = max(1, $paged);
    
    if (is_home()) {
        $base = get_option('page_for_posts') 
            ? get_permalink(get_option('page_for_posts')) 
            : home_url('/');
    } elseif (is_category()) {
        $base = get_category_link(get_queried_object_id());
    } elseif (is_tag()) {
        $base = get_tag_link(get_queried_object_id());
    } elseif (is_author()) {
        $base = get_author_posts_url(get_queried_object_id());
    } else {
        $base = get_pagenum_link(1);
    }
    
    if ($current_page > 1) {
        $canonical = trailingslashit($base) . 'page/' . $current_page . '/';
    } else {
        $canonical = $base;
    }
    
    // WordPress'in kendi canonical çıktısını devre dışı bırak
    remove_action('wp_head', 'rel_canonical');
    
    echo '<link rel="canonical" href="' . esc_url($canonical) . '" />' . "n";
}

Özel Post Type’lar için Canonical

Eğer custom post type’larınız varsa, bunlar için de özel canonical mantığı kurmanız gerekebilir. Diyelim ki “portfolio” adında bir CPT’niz var ve hem /portfolio/proje-adi/ hem de /proje-adi/ URL’si çalışıyor:

<?php
add_filter('get_canonical_url', 'cpt_canonical_fix', 20, 2);

function cpt_canonical_fix($canonical, $post) {
    
    $custom_post_types = array('portfolio', 'urun-dokuman', 'vaka-calismasi');
    
    if (!in_array($post->post_type, $custom_post_types)) {
        return $canonical;
    }
    
    // Her zaman özel post type'ın kendi permalink'ini kullan
    $correct_permalink = get_permalink($post->ID);
    
    // URL protokolünü site ayarına göre normalize et
    $site_url = get_option('siteurl');
    $protocol = strpos($site_url, 'https') === 0 ? 'https' : 'http';
    
    $correct_permalink = preg_replace('/^https?/', $protocol, $correct_permalink);
    
    return $correct_permalink;
}

Canonical ile Birlikte Kullanılması Gereken: Hreflang

Çok dilli siteler için canonical ve hreflang birlikte çalışmalıdır. WPML veya Polylang kullanıyorsanız şu yapıyı kurun:

<?php
add_action('wp_head', 'multilingual_canonical_and_hreflang', 1);

function multilingual_canonical_and_hreflang() {
    
    if (!function_exists('pll_get_post') && !defined('ICL_SITEPRESS_VERSION')) {
        return;
    }
    
    global $post;
    
    if (!is_singular() || !$post) {
        return;
    }
    
    // Mevcut dilin canonical'ı
    $canonical = get_permalink($post->ID);
    remove_action('wp_head', 'rel_canonical');
    
    echo '<link rel="canonical" href="' . esc_url($canonical) . '" />' . "n";
    
    // Polylang kullanıyorsa hreflang ekle
    if (function_exists('pll_the_languages')) {
        $languages = pll_the_languages(array('raw' => 1));
        
        foreach ($languages as $lang) {
            if (!empty($lang['url'])) {
                $post_translation = pll_get_post($post->ID, $lang['slug']);
                if ($post_translation) {
                    $translation_url = get_permalink($post_translation);
                    echo '<link rel="alternate" hreflang="' . esc_attr($lang['locale']) . '" href="' . esc_url($translation_url) . '" />' . "n";
                }
            }
        }
        
        // x-default hreflang
        $default_lang_post = pll_get_post($post->ID, pll_default_language());
        if ($default_lang_post) {
            echo '<link rel="alternate" hreflang="x-default" href="' . esc_url(get_permalink($default_lang_post)) . '" />' . "n";
        }
    }
}

Canonical URL Doğrulama Fonksiyonu

Yazdığınız canonical mantığının doğru çalışıp çalışmadığını test etmek için admin toolbar’a veya bir shortcode’a debug çıktısı ekleyebilirsiniz:

<?php
// Sadece geliştirme ortamında kullan
add_action('wp_footer', 'debug_canonical_url');

function debug_canonical_url() {
    
    // Sadece admin kullanıcılar görsün ve sadece dev ortamında çalışsın
    if (!current_user_can('manage_options') || !WP_DEBUG) {
        return;
    }
    
    global $wp_query;
    $object = $wp_query->get_queried_object();
    
    $debug_info = array();
    
    // WordPress'in belirlediği canonical
    if ($object instanceof WP_Post) {
        $debug_info['wp_canonical'] = get_permalink($object->ID);
        $debug_info['post_type'] = $object->post_type;
        $debug_info['post_id'] = $object->ID;
    }
    
    $debug_info['current_url'] = (is_ssl() ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
    $debug_info['is_singular'] = is_singular() ? 'evet' : 'hayir';
    $debug_info['is_archive'] = is_archive() ? 'evet' : 'hayir';
    $debug_info['paged'] = get_query_var('paged');
    
    echo '<script>console.group("Canonical Debug");';
    foreach ($debug_info as $key => $value) {
        echo 'console.log("' . esc_js($key) . ':", "' . esc_js($value) . '");';
    }
    echo 'console.groupEnd();</script>';
}

Bu fonksiyon, tarayıcı konsoluna canonical bilgilerini yazdırır. Sitenin farklı sayfalarında gezinerek canonical URL’lerin doğru üretilip üretilmediğini kontrol edebilirsiniz.

Sık Karşılaşılan Hatalar ve Çözümleri

Çift canonical etiketi: En yaygın sorun budur. Hem WordPress’in varsayılan canonical’ı hem de sizin fonksiyonunuz aynı anda çıktı üretiyordur. Çözüm: remove_action('wp_head', 'rel_canonical') satırını her zaman ekleyin.

HTTPS/HTTP karışıklığı: Bazen get_permalink() HTTP döndürürken siteniz HTTPS üzerinde çalışıyor. Bunu önlemek için:

  • Permalink’i aldıktan sonra set_url_scheme($url, 'https') fonksiyonunu kullanın
  • WordPress’in siteurl seçeneğinin HTTPS ile başladığından emin olun

Trailing slash tutarsızlığı: Bazı URL’ler / ile biterken bazıları bitmez. WordPress admin panelinde “Ayarlar > Kalıcı Bağlantılar” yapılandırmanızla tutarlı olun. trailingslashit() veya untrailingslashit() fonksiyonlarını tutarlı biçimde kullanın.

WooCommerce filtresi ile çakışma: WooCommerce kendi canonical filtrelerini çok düşük öncelikle (priority 10) ekler. Sizin filtre önceliğinizi 20 veya daha yüksek yaparsanız WooCommerce’in yazdıklarının üzerine yazarsınız.

Staging ortamı sızması: Staging siteniz production canonical URL’ini kullanmalı, kendi URL’ini değil. Bunu yönetmek için:

<?php
add_filter('get_canonical_url', 'force_production_canonical', 99);

function force_production_canonical($canonical) {
    
    $staging_domain = 'staging.siteniz.com';
    $production_domain = 'www.siteniz.com';
    
    // Canonical URL'deki staging domain'i production ile değiştir
    $canonical = str_replace($staging_domain, $production_domain, $canonical);
    
    // HTTP'yi HTTPS'e çevir
    $canonical = str_replace('http://' . $production_domain, 'https://' . $production_domain, $canonical);
    
    return $canonical;
}

Bu kodu sadece staging ortamında aktif etmek için wp-config.php içinde bir sabit tanımlayabilirsiniz.

Canonical URL’leri Düzenli Test Etme

Tüm bu kodları yazdıktan sonra düzenli test rutini oluşturmak şarttır. Sysadmin olarak şunu söyleyeyim: Yazan kişi kontrolü yapmazsa yanlış giden şeyi fark eden olmaz.

Test etmeniz gereken senaryolar:

  • Ana sayfa (/) için canonical
  • Blog listesi sayfası ve sayfalı versiyonları (/blog/page/2/)
  • Tekil yazı/sayfa canonical’ları
  • Kategori ve etiket arşivleri
  • WooCommerce mağaza sayfası ve filtrelenmiş versiyonları
  • Ürün sayfaları ve varyasyon URL’leri
  • Arama sonuçları sayfası (?s=kelime)
  • 404 sayfaları (canonical olmamalı)

Google Search Console’un “URL İnceleme” aracı, herhangi bir URL için canonical etiketini doğrudan gösterir. Screaming Frog gibi araçlar ise toplu tarama yaparak tüm siteyi analiz eder.

Canonical URL konfigürasyonu bir kez yapılıp unutulacak bir şey değildir. Yeni özellik eklendikçe, tema değiştirildikçe veya WooCommerce güncellemelerinden sonra mutlaka kontrol edilmelidir.

Sonuç

functions.php üzerinden canonical URL yönetimi, SEO’nun en teknik ama en etkili alanlarından biridir. Eklentilere tamamen bıraktığınızda kontrolü kaybedersiniz; kendi kodunuzu yazdığınızda ise tam olarak ne olduğunu bilirsiniz.

Bu yazıda anlattıkları uygulamak için önerilen sıra şudur: Önce mevcut siteyi Screaming Frog ile tarayın ve canonical sorunlarını listeleyin. Sonra child theme’inizin functions.php dosyasına adım adım ekleyin, her adımda tarayıcı konsolunu ve sayfa kaynağını kontrol edin. WooCommerce kullanıyorsanız mağaza, kategori ve ürün sayfalarını ayrı ayrı test edin. Son olarak Google Search Console’da URL İnceleme aracını kullanarak kritik sayfaları doğrulayın.

Canonical URL yönetimini doğru kurguladığınızda arama motoru tarama bütçenizi korumuş, PageRank dağılmasını önlemiş ve indeksleme kalitesini artırmış olursunuz. Bu üç faktör uzun vadede organik trafiğe doğrudan yansır. Teknik SEO, sysadmin işidir; ve iyi bir sysadmin altyapıyı sağlam kurar.

Bir yanıt yazın

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