WordPress’te Eklentisiz Meta Description ve Canonical URL Ekleme
WordPress sitenizde SEO için her şeyi bir eklentiye bırakmak zorunda değilsiniz. Yoast, Rank Math gibi eklentiler harika araçlar ama beraberinde ciddi bir yük getiriyorlar. Sadece meta description ve canonical URL eklemek için onlarca özellik yüklemek, veritabanını şişirmek ve sayfa yüklenme süresini artırmak çoğu zaman gereksiz. Özellikle küçük projeler, özel temalar ya da performans odaklı siteler için functions.php üzerinden bu işi kendiniz halletmek çok daha temiz bir çözüm.
Bu yazıda, eklenti kullanmadan WordPress’e nasıl meta description ve canonical URL ekleyeceğinizi, bunu yaparken nelere dikkat etmeniz gerektiğini ve gerçek dünya senaryolarında nasıl özelleştirebileceğinizi ele alacağız.
Neden Eklentisiz Yapmak İsteyebilirsiniz?
Önce motivasyonu netleştirelim. SEO eklentileri gerçekten çok şey yapıyor; schema markup, breadcrumb, sitemap, sosyal medya meta etiketleri ve daha fazlası. Ama bazı durumlarda bu özelliklerden sadece ikisine ihtiyacınız var: meta description ve canonical URL. Bu iki şey için bir eklenti yüklemek aşırıya kaçmak olabilir.
- Hafif, özelleştirilmiş temalar geliştiriyorsanız
- Ajans olarak müşteri sitelerini yönetiyorsanız ve eklenti sayısını minimumda tutmak istiyorsanız
- Headless WordPress ya da API odaklı bir yapı kuruyorsanız
- Mevcut eklentiniz belirli post type’lar için yeterli destek vermiyorsa
Bu senaryoların herhangi birindeyseniz, functions.php yolunu tercih etmek son derece mantıklı.
Temel Yapıyı Anlamak
WordPress, bölümüne müdahale etmek için wp_head hook’unu sunar. Bizim yapacağımız şey bu hook’a bir fonksiyon bağlamak ve sayfa türüne göre doğru meta etiketlerini output olarak vermek.
Meta description için temel mantık şu: Her sayfa için önce özel olarak girilmiş bir açıklama var mı diye bakıyoruz, yoksa otomatik olarak içerikten üretiyoruz.
Canonical URL için ise WordPress’in kendi get_permalink() ve benzeri fonksiyonlarını kullanarak doğru URL’yi belirliyoruz.
İlk Adım: Basit Meta Description Fonksiyonu
Hemen çalışan bir versiyon ile başlayalım:
function custom_meta_description() {
global $post;
$description = '';
if ( is_singular() ) {
if ( has_excerpt( $post->ID ) ) {
$description = get_the_excerpt( $post->ID );
} elseif ( ! empty( $post->post_content ) ) {
$description = wp_strip_all_tags( $post->post_content );
$description = str_replace( array("rn", "r", "n"), ' ', $description );
$description = substr( $description, 0, 160 );
}
} elseif ( is_home() || is_front_page() ) {
$description = get_bloginfo( 'description' );
} elseif ( is_category() || is_tag() || is_tax() ) {
$description = term_description();
$description = wp_strip_all_tags( $description );
} elseif ( is_author() ) {
$description = get_the_author_meta( 'description' );
}
if ( ! empty( $description ) ) {
$description = esc_attr( trim( $description ) );
echo '<meta name="description" content="' . $description . '">' . "n";
}
}
add_action( 'wp_head', 'custom_meta_description' );
Bu fonksiyon tekil sayfalarda önce excerpt’e bakıyor, yoksa içerikten ilk 160 karakteri alıyor. Ana sayfa için site açıklamasını, kategori/tag/taksonomi sayfaları için term açıklamasını, yazar sayfaları için yazar biyografisini kullanıyor.
Canonical URL Ekleme
Şimdi canonical URL ekleyelim. Canonical URL, arama motorlarına “bu sayfanın asıl adresi budur” diye söylemenin yolu. Özellikle pagination, query string’ler ya da birden fazla URL ile erişilebilen içerikler için kritik öneme sahip.
function custom_canonical_url() {
$canonical = '';
if ( is_singular() ) {
$canonical = get_permalink();
} elseif ( is_home() ) {
if ( 'page' == get_option( 'show_on_front' ) ) {
$canonical = get_permalink( get_option( 'page_for_posts' ) );
} else {
$canonical = get_bloginfo( 'url' );
}
} elseif ( is_front_page() ) {
$canonical = get_bloginfo( 'url' ) . '/';
} elseif ( is_category() || is_tag() || is_tax() ) {
$canonical = get_term_link( get_queried_object() );
} elseif ( is_author() ) {
$canonical = get_author_posts_url( get_queried_object_id() );
} elseif ( is_search() ) {
$canonical = get_search_link();
} elseif ( is_archive() ) {
$canonical = get_the_archive_link();
}
if ( ! empty( $canonical ) && ! is_wp_error( $canonical ) ) {
echo '<link rel="canonical" href="' . esc_url( $canonical ) . '">' . "n";
}
}
add_action( 'wp_head', 'custom_canonical_url' );
Burada dikkat etmeniz gereken nokta is_wp_error() kontrolü. get_term_link() bazen WP_Error döndürebilir, bunu yakalamadan direkt esc_url() içine koyarsanız hata alırsınız.
Gelişmiş Versiyon: Özel Alan Desteği
Gerçek dünyada editörlerin veya içerik yöneticilerinin her sayfa için özel meta description girebilmesini istersiniz. Bunun için post meta kullanıyoruz.
function custom_save_meta_description( $post_id ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
if ( ! isset( $_POST['custom_meta_description_nonce'] ) ) return;
if ( ! wp_verify_nonce( $_POST['custom_meta_description_nonce'], 'custom_meta_description' ) ) return;
if ( isset( $_POST['custom_meta_description'] ) ) {
$description = sanitize_text_field( $_POST['custom_meta_description'] );
update_post_meta( $post_id, '_custom_meta_description', $description );
}
}
add_action( 'save_post', 'custom_save_meta_description' );
function custom_meta_description_box() {
add_meta_box(
'custom_meta_description',
'SEO Meta Description',
'custom_meta_description_box_callback',
array( 'post', 'page', 'product' ),
'normal',
'high'
);
}
add_action( 'add_meta_boxes', 'custom_meta_description_box' );
function custom_meta_description_box_callback( $post ) {
wp_nonce_field( 'custom_meta_description', 'custom_meta_description_nonce' );
$value = get_post_meta( $post->ID, '_custom_meta_description', true );
echo '<textarea name="custom_meta_description" rows="3" style="width:100%;">';
echo esc_textarea( $value );
echo '</textarea>';
echo '<p class="description">Boş bırakırsanız otomatik oluşturulur. Maksimum 160 karakter önerilir.</p>';
}
Bu kod editör arayüzüne “SEO Meta Description” başlıklı bir meta box ekliyor. Post, page ve WooCommerce product post type’larında görünüyor.
Meta Description Fonksiyonunu Özel Alanla Entegre Etme
Şimdi bu özel alanı meta description fonksiyonuna dahil edelim:
function custom_meta_description_v2() {
global $post;
$description = '';
if ( is_singular() ) {
// Önce özel alana bak
$custom = get_post_meta( $post->ID, '_custom_meta_description', true );
if ( ! empty( $custom ) ) {
$description = $custom;
} elseif ( has_excerpt( $post->ID ) ) {
$description = get_the_excerpt( $post->ID );
$description = wp_strip_all_tags( $description );
} else {
$description = wp_strip_all_tags( $post->post_content );
$description = preg_replace( '/s+/', ' ', $description );
$description = trim( $description );
if ( mb_strlen( $description ) > 160 ) {
$description = mb_substr( $description, 0, 157 ) . '...';
}
}
} elseif ( is_home() || is_front_page() ) {
$description = get_bloginfo( 'description' );
} elseif ( is_category() || is_tag() || is_tax() ) {
$term_desc = term_description();
if ( ! empty( $term_desc ) ) {
$description = wp_strip_all_tags( $term_desc );
$description = trim( $description );
if ( mb_strlen( $description ) > 160 ) {
$description = mb_substr( $description, 0, 157 ) . '...';
}
}
} elseif ( is_author() ) {
$description = get_the_author_meta( 'description' );
} elseif ( is_archive() ) {
$description = get_bloginfo( 'name' ) . ' - ' . get_the_archive_title();
}
if ( ! empty( $description ) ) {
echo '<meta name="description" content="' . esc_attr( trim( $description ) ) . '">' . "n";
}
}
add_action( 'wp_head', 'custom_meta_description_v2' );
mb_strlen() ve mb_substr() kullanımına dikkat edin. Türkçe karakterler için strlen() yerine multibyte versiyonlarını kullanmak zorunlu, aksi halde karakter sayısı yanlış hesaplanır.
WooCommerce Ürünleri için Özel Yaklaşım
WooCommerce ürünlerinde meta description için ürünün kısa açıklamasını kullanmak mantıklı:
function custom_woo_meta_description() {
if ( ! function_exists( 'is_product' ) ) return;
if ( is_product() ) {
global $post;
$custom = get_post_meta( $post->ID, '_custom_meta_description', true );
if ( ! empty( $custom ) ) {
$description = esc_attr( $custom );
} else {
// WooCommerce kısa açıklaması
$short_desc = get_post_meta( $post->ID, '_product_short_description', true );
// Alternatif olarak post excerpt
if ( empty( $short_desc ) ) {
$short_desc = $post->post_excerpt;
}
// O da yoksa içerik
if ( empty( $short_desc ) ) {
$short_desc = $post->post_content;
}
$description = wp_strip_all_tags( $short_desc );
$description = preg_replace( '/s+/', ' ', $description );
$description = trim( $description );
if ( mb_strlen( $description ) > 160 ) {
$description = mb_substr( $description, 0, 157 ) . '...';
}
$description = esc_attr( $description );
}
if ( ! empty( $description ) ) {
echo '<meta name="description" content="' . $description . '">' . "n";
}
// Ürün için canonical
$canonical = get_permalink( $post->ID );
echo '<link rel="canonical" href="' . esc_url( $canonical ) . '">' . "n";
}
}
add_action( 'wp_head', 'custom_woo_meta_description', 5 );
Bu fonksiyonu diğerinden önce çalıştırmak için priority değerini 5 verdik. Eğer genel fonksiyonla çakışma yaşarsanız bu öncelik sırasını ayarlamanız gerekebilir.
Pagination Durumunda Canonical URL
Sayfalama olan arşiv sayfalarında canonical URL yönetimi biraz daha karmaşık:
function custom_canonical_with_pagination() {
global $wp_query, $paged;
$canonical = '';
if ( is_singular() ) {
$canonical = get_permalink();
} elseif ( is_home() || is_front_page() ) {
if ( $paged > 1 ) {
$canonical = get_pagenum_link( $paged );
} else {
$canonical = home_url( '/' );
}
} elseif ( is_category() || is_tag() || is_tax() ) {
$term = get_queried_object();
if ( $paged > 1 ) {
$canonical = get_pagenum_link( $paged );
} else {
$term_link = get_term_link( $term );
if ( ! is_wp_error( $term_link ) ) {
$canonical = $term_link;
}
}
} elseif ( is_author() ) {
if ( $paged > 1 ) {
$canonical = get_pagenum_link( $paged );
} else {
$canonical = get_author_posts_url( get_queried_object_id() );
}
} elseif ( is_search() ) {
$canonical = get_search_link();
} elseif ( is_404() ) {
return; // 404 sayfasına canonical koymuyoruz
}
if ( ! empty( $canonical ) ) {
echo '<link rel="canonical" href="' . esc_url( $canonical ) . '">' . "n";
}
}
add_action( 'wp_head', 'custom_canonical_with_pagination' );
404 sayfalarına canonical eklemediğimize dikkat edin. Mantıklı olan bu; arama motoruna var olmayan bir sayfanın canonical’ı olduğunu söylemek anlamsız.
Mevcut Canonical Etiketini Kaldırma
WordPress bazı temalar ve eklentiler zaten canonical ekleyebiliyor. Çakışmayı önlemek için mevcut WordPress canonical çıktısını kaldırmanız gerekebilir:
// WordPress'in varsayılan canonical çıktısını kaldır
remove_action( 'wp_head', 'rel_canonical' );
// Bazı temalarda bu da gerekebilir
add_filter( 'wpseo_canonical', '__return_false' ); // Yoast
add_filter( 'rank_math/frontend/canonical', '__return_false' ); // Rank Math
Bu satırları functions.php dosyanızın başına ekleyin. Eğer hem Yoast hem de kendi kodunuz çalışıyorsa sayfada iki canonical etiketi olur ve bu SEO açısından sorunlu.
Hepsini Bir Arada Yönetme: Temiz Sınıf Yapısı
Tüm bu fonksiyonları tek bir sınıfta toplamak daha iyi bir pratik:
if ( ! class_exists( 'CustomSEOMeta' ) ) {
class CustomSEOMeta {
public function __construct() {
remove_action( 'wp_head', 'rel_canonical' );
add_action( 'wp_head', array( $this, 'output_meta' ), 1 );
add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) );
add_action( 'save_post', array( $this, 'save_meta' ) );
}
public function output_meta() {
$description = $this->get_description();
$canonical = $this->get_canonical();
if ( ! empty( $description ) ) {
echo '<meta name="description" content="' . esc_attr( $description ) . '">' . "n";
}
if ( ! empty( $canonical ) ) {
echo '<link rel="canonical" href="' . esc_url( $canonical ) . '">' . "n";
}
}
private function get_description() {
global $post;
$description = '';
if ( is_singular() && $post ) {
$custom = get_post_meta( $post->ID, '_seo_meta_description', true );
if ( ! empty( $custom ) ) {
return trim( $custom );
}
if ( has_excerpt( $post->ID ) ) {
$description = wp_strip_all_tags( get_the_excerpt( $post->ID ) );
} else {
$description = wp_strip_all_tags( $post->post_content );
$description = preg_replace( '/s+/', ' ', $description );
}
$description = trim( $description );
return mb_strlen( $description ) > 160 ? mb_substr( $description, 0, 157 ) . '...' : $description;
}
if ( is_home() || is_front_page() ) {
return get_bloginfo( 'description' );
}
if ( is_category() || is_tag() || is_tax() ) {
return wp_strip_all_tags( term_description() );
}
if ( is_author() ) {
return get_the_author_meta( 'description' );
}
return $description;
}
private function get_canonical() {
global $paged;
if ( is_singular() ) return get_permalink();
if ( is_home() || is_front_page() ) {
return $paged > 1 ? get_pagenum_link( $paged ) : home_url( '/' );
}
if ( is_category() || is_tag() || is_tax() ) {
$link = $paged > 1 ? get_pagenum_link( $paged ) : get_term_link( get_queried_object() );
return is_wp_error( $link ) ? '' : $link;
}
if ( is_author() ) return get_author_posts_url( get_queried_object_id() );
if ( is_search() ) return get_search_link();
if ( is_404() ) return '';
return '';
}
public function add_meta_box() {
$screens = array( 'post', 'page', 'product' );
add_meta_box(
'seo_meta_description',
'SEO Meta Description',
array( $this, 'meta_box_callback' ),
$screens,
'normal',
'high'
);
}
public function meta_box_callback( $post ) {
wp_nonce_field( 'seo_meta_description_save', 'seo_meta_description_nonce' );
$value = get_post_meta( $post->ID, '_seo_meta_description', true );
?>
<textarea name="seo_meta_description" rows="3" style="width:100%;font-size:14px;"><?php echo esc_textarea( $value ); ?></textarea>
<p class="description">Önerilen uzunluk: 120-160 karakter. Boş bırakırsanız otomatik oluşturulur.</p>
<p class="description"><strong>Mevcut karakter sayısı:</strong> <span id="meta-char-count"><?php echo mb_strlen( $value ); ?></span></p>
<script>
document.querySelector('[name="seo_meta_description"]').addEventListener('input', function(){
document.getElementById('meta-char-count').textContent = this.value.length;
});
</script>
<?php
}
public function save_meta( $post_id ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
if ( ! isset( $_POST['seo_meta_description_nonce'] ) ) return;
if ( ! wp_verify_nonce( $_POST['seo_meta_description_nonce'], 'seo_meta_description_save' ) ) return;
if ( array_key_exists( 'seo_meta_description', $_POST ) ) {
update_post_meta(
$post_id,
'_seo_meta_description',
sanitize_text_field( $_POST['seo_meta_description'] )
);
}
}
}
new CustomSEOMeta();
}
Dikkat Edilmesi Gereken Noktalar
Tema içindeki mevcut canonical: get_template_directory() ve get_stylesheet_directory() ile bulabileceğiniz header.php dosyasına bakın, bazı temalar buraya hardcoded canonical ya da meta tag koymuş olabilir.
Child theme kullanımı: Bu kodları doğrudan temanın functions.php dosyasına yazmak, tema güncellemesinde değişikliklerin kaybolmasına neden olur. Mutlaka child theme functions.php dosyasına ya da özel bir plugin’e yazın.
Çakışma testi: Kodu ekledikten sonra tarayıcıda sayfanın kaynak kodunu açın (Ctrl + U) ve bölümünde kaç tane meta name="description" ya da link rel="canonical" olduğuna bakın. Birden fazlaysa bir yerde çakışma var demek.
is_plugin_active() kontrolü: Eğer Yoast veya Rank Math kurulu sitelerde de bu kodu çalıştırmak istiyorsanız, kontrol ekleyebilirsiniz:
if ( ! is_plugin_active( 'wordpress-seo/wp-seo.php' ) &&
! is_plugin_active( 'seo-by-rank-math/rank-math.php' ) ) {
new CustomSEOMeta();
}
Bu sayede eklenti varken kodunuz devreye girmez, eklenti devre dışı bırakılınca otomatik aktifleşir.
Sonuç
Gördüğünüz gibi meta description ve canonical URL eklemek için Yoast’a ya da herhangi bir SEO eklentisine ihtiyacınız yok. Birkaç doğru fonksiyon ve wp_head hook’u ile bunu tamamen kendiniz yönetebilirsiniz.
Bu yaklaşımın getirdiği avantajlar somut: daha az veritabanı sorgusu, daha hızlı sayfa yüklenme, daha az bağımlılık. Özellikle birden fazla WordPress sitesi yöneten sysadminler ve geliştiriciler için bu kodu bir “standart parçası” olarak kendi snippets kütüphanelerine eklemesi oldukça mantıklı.
Tabii gerçek anlamda kapsamlı SEO’ya ihtiyacınız varsa, yani schema markup, Open Graph, Twitter Card, XML sitemap gibi özellikler de istiyorsanız, o zaman bir SEO eklentisi kurmak daha verimli olabilir. Ama sadece bu iki temel özellik için eklenti yüklemek, kurulan siteye gereksiz yük eklemek demek. Kararı iş gereksinimlerine göre vermek en doğrusu.
