WordPress’te İlgili Yazılar Bölümü Ekleme: functions.php ile
WordPress sitenizde ziyaretçi okumayı bitirip sayfayı kapatmak üzereyken, ekranın altında ilgili yazılar bölümü beliriyor ve “dur bir dakika, bunu da okumam lazım” dedirtiyor. Bu basit ama güçlü mekanizma, hem kullanıcı deneyimini iyileştiriyor hem de sayfa görüntüleme sayınızı ciddi ölçüde artırıyor. Hazır eklenti kurmak yerine functions.php üzerinden bu sistemi kendiniz kurarsanız hem siteniz şişmez hem de tam istediğiniz gibi özelleştirirsiniz.
İlgili Yazılar Neden Önemli?
Bir blog düşünün: ziyaretçi “WordPress cache nasıl temizlenir” başlıklı yazınızı okuyor. Yazının altında “Redis ile WordPress Hızlandırma” veya “W3 Total Cache Kurulumu” gibi ilgili içerikler görünce siteyi terk etmek yerine okumaya devam ediyor. Bu, bounce rate (hemen çıkma oranı) düşürmenin en organik yollarından biri.
Hazır eklentiler (Related Posts by Jetpack, YARPP vs.) genellikle veritabanını ağır sorgularla yoruyor, sitenizi yavaşlatıyor ve fazladan HTTP isteği yaratıyor. functions.php ile yazacağınız özel kod sadece sizin ihtiyacınıza göre çalışır, gereksiz tablo sorgusu atmaz, tema değişikliklerine karşı dirençli kalır.
Temel Yaklaşım: Kategori Tabanlı İlgili Yazılar
En yaygın ve en verimli yöntem, mevcut yazının kategorisindeki diğer yazıları çekmektir. WordPress’in WP_Query sınıfı bu iş için biçilmiş kaftan.
İlk Fonksiyon: Basit Kategori Tabanlı Sorgu
// functions.php
function sysadmin_ilgili_yazilar( $post_id = null, $adet = 5 ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
// Mevcut yazının kategorilerini al
$kategoriler = wp_get_post_categories( $post_id );
if ( empty( $kategoriler ) ) {
return array();
}
$args = array(
'category__in' => $kategoriler,
'post__not_in' => array( $post_id ), // Kendisini gösterme
'posts_per_page' => $adet,
'orderby' => 'rand', // Rastgele sırala
'post_status' => 'publish',
);
$sorgu = new WP_Query( $args );
wp_reset_postdata();
return $sorgu->posts;
}
Bu fonksiyon temel ihtiyacı karşılıyor: mevcut yazının kategorisindeki yayınlanmış yazıları çekiyor, kendisini listeye dahil etmiyor. orderby => rand kullandığımız için her sayfa yenilemede farklı yazılar görünüyor, bu da ziyaretçinin her gelişinde yeni içerik keşfetmesini sağlıyor.
Fonksiyonu Tema Şablonunda Kullanmak
// single.php veya content-single.php içinde
$ilgili_yazilar = sysadmin_ilgili_yazilar( get_the_ID(), 4 );
if ( ! empty( $ilgili_yazilar ) ) {
echo '<div class="ilgili-yazilar">';
echo '<h3>İlgili Yazılar</h3>';
echo '<ul>';
foreach ( $ilgili_yazilar as $yazi ) {
$thumb = get_the_post_thumbnail_url( $yazi->ID, 'thumbnail' );
echo '<li>';
echo '<a href="' . get_permalink( $yazi->ID ) . '">';
if ( $thumb ) {
echo '<img src="' . esc_url( $thumb ) . '" alt="' . esc_attr( $yazi->post_title ) . '">';
}
echo '<span>' . esc_html( $yazi->post_title ) . '</span>';
echo '</a>';
echo '</li>';
}
echo '</ul>';
echo '</div>';
}
Etiket Tabanlı İlgili Yazılar: Daha Hassas Eşleşme
Kategoriler geniş kapsamlı gruplandırmalar yapar ama etiketler çok daha spesifik. “wordpress-cache” etiketi olan bir yazının ilgili yazıları, “genel wordpress” kategorisindekilerden çok daha alakalı olacaktır.
// functions.php
function sysadmin_etiket_ilgili_yazilar( $post_id = null, $adet = 5 ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$etiketler = wp_get_post_tags( $post_id, array( 'fields' => 'ids' ) );
if ( empty( $etiketler ) ) {
// Etiket yoksa kategoriye düş
return sysadmin_ilgili_yazilar( $post_id, $adet );
}
$args = array(
'tag__in' => $etiketler,
'post__not_in' => array( $post_id ),
'posts_per_page' => $adet,
'orderby' => 'relevance', // Etiket eşleşmesine göre sırala
'post_status' => 'publish',
'ignore_sticky_posts' => true,
);
$sorgu = new WP_Query( $args );
$yazilar = $sorgu->posts;
wp_reset_postdata();
// Yeterli yazı bulunamadıysa kategoriyle tamamla
if ( count( $yazilar ) < $adet ) {
$eksik_adet = $adet - count( $yazilar );
$mevcut_ids = array_map( function( $y ) { return $y->ID; }, $yazilar );
$mevcut_ids[] = $post_id;
$kategoriler = wp_get_post_categories( $post_id );
if ( ! empty( $kategoriler ) ) {
$ek_args = array(
'category__in' => $kategoriler,
'post__not_in' => $mevcut_ids,
'posts_per_page' => $eksik_adet,
'orderby' => 'date',
'post_status' => 'publish',
);
$ek_sorgu = new WP_Query( $ek_args );
$yazilar = array_merge( $yazilar, $ek_sorgu->posts );
wp_reset_postdata();
}
}
return $yazilar;
}
Bu fonksiyon akıllı bir fallback mekanizmasına sahip: etiketle 5 yazı bulamazsa eksik kalan kısmı kategori yazılarıyla tamamlıyor. Küçük siteler için bu kritik, yoksa ilgili yazılar bölümü sürekli boş görünebilir.
Shortcode ile İçerik Editöründen Kullanmak
Bazen ilgili yazıları yazının ortasına veya belirli bir konumuna yerleştirmek isteyebilirsiniz. Bunun için shortcode harika bir çözüm.
// functions.php
function sysadmin_ilgili_shortcode( $atts ) {
$atts = shortcode_atts(
array(
'adet' => 3,
'baslik' => 'İlgili Yazılar',
'stil' => 'liste', // liste veya kart
),
$atts,
'ilgili_yazilar'
);
$post_id = get_the_ID();
$yazilar = sysadmin_etiket_ilgili_yazilar( $post_id, intval( $atts['adet'] ) );
if ( empty( $yazilar ) ) {
return '';
}
$cikti = '<div class="ilgili-yazilar ilgili-yazilar--' . esc_attr( $atts['stil'] ) . '">';
$cikti .= '<h4 class="ilgili-yazilar__baslik">' . esc_html( $atts['baslik'] ) . '</h4>';
if ( $atts['stil'] === 'kart' ) {
$cikti .= '<div class="ilgili-yazilar__grid">';
foreach ( $yazilar as $yazi ) {
$thumb = get_the_post_thumbnail_url( $yazi->ID, 'medium' );
$tarih = get_the_date( 'd.m.Y', $yazi->ID );
$cikti .= '<div class="ilgili-kart">';
if ( $thumb ) {
$cikti .= '<img src="' . esc_url( $thumb ) . '" alt="">';
}
$cikti .= '<div class="ilgili-kart__icerik">';
$cikti .= '<a href="' . get_permalink( $yazi->ID ) . '">' . esc_html( $yazi->post_title ) . '</a>';
$cikti .= '<span class="ilgili-kart__tarih">' . $tarih . '</span>';
$cikti .= '</div></div>';
}
$cikti .= '</div>';
} else {
$cikti .= '<ul>';
foreach ( $yazilar as $yazi ) {
$cikti .= '<li><a href="' . get_permalink( $yazi->ID ) . '">' . esc_html( $yazi->post_title ) . '</a></li>';
}
$cikti .= '</ul>';
}
$cikti .= '</div>';
return $cikti;
}
add_shortcode( 'ilgili_yazilar', 'sysadmin_ilgili_shortcode' );
Editörde kullanımı şöyle:
[ilgili_yazilar adet="4" baslik="Bunları da okuyun" stil="kart"]
Performans Optimizasyonu: Transient Cache
rand ile her yüklemede veritabanına sorgu atmak yoğun trafikli sitelerde sorun yaratır. Transient kullanarak sonuçları önbelleğe alalım.
// functions.php
function sysadmin_ilgili_yazilar_cached( $post_id = null, $adet = 5, $ttl = 3600 ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$cache_key = 'ilgili_yazilar_' . $post_id . '_' . $adet;
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
// Cache yoksa sorguyu çalıştır
$yazilar = sysadmin_etiket_ilgili_yazilar( $post_id, $adet );
// 1 saat (3600 saniye) önbellekte tut
set_transient( $cache_key, $yazilar, $ttl );
return $yazilar;
}
// Yazı güncellenince cache'i temizle
function sysadmin_ilgili_cache_temizle( $post_id ) {
// 3, 5 ve 10 adet varyasyonları için temizle
foreach ( array( 3, 4, 5, 6, 10 ) as $adet ) {
delete_transient( 'ilgili_yazilar_' . $post_id . '_' . $adet );
}
}
add_action( 'save_post', 'sysadmin_ilgili_cache_temizle' );
add_action( 'deleted_post', 'sysadmin_ilgili_cache_temizle' );
Bu yapı şunu sağlıyor: bir yazı ilk kez yüklendiğinde sorgu atıyor, sonucu 1 saat transient olarak saklıyor. Aynı yazı tekrar yüklendiğinde veritabanına dokunmadan önbellekten dönüyor. Yazı güncellenince o yazının cache’i otomatik temizleniyor.
Widget Olarak İlgili Yazılar
Bazı temalar sidebar ya da footer widget alanlarına sahiptir. İlgili yazıları buraya da atabiliriz.
// functions.php
class SysAdmin_Ilgili_Yazilar_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'sysadmin_ilgili_widget',
'İlgili Yazılar (Özel)',
array( 'description' => 'Mevcut yazıyla ilgili içerikleri gösterir.' )
);
}
public function widget( $args, $instance ) {
if ( ! is_single() ) {
return; // Sadece tekil yazı sayfasında göster
}
$baslik = ! empty( $instance['baslik'] ) ? $instance['baslik'] : 'İlgili Yazılar';
$adet = ! empty( $instance['adet'] ) ? intval( $instance['adet'] ) : 5;
$yazilar = sysadmin_ilgili_yazilar_cached( get_the_ID(), $adet );
if ( empty( $yazilar ) ) {
return;
}
echo $args['before_widget'];
echo $args['before_title'] . esc_html( $baslik ) . $args['after_title'];
echo '<ul class="ilgili-widget-listesi">';
foreach ( $yazilar as $yazi ) {
$thumb = get_the_post_thumbnail_url( $yazi->ID, 'thumbnail' );
echo '<li class="ilgili-widget-item">';
if ( $thumb ) {
echo '<img src="' . esc_url( $thumb ) . '" width="60" alt="">';
}
echo '<a href="' . get_permalink( $yazi->ID ) . '">' . esc_html( $yazi->post_title ) . '</a>';
echo '</li>';
}
echo '</ul>';
echo $args['after_widget'];
}
public function update( $new_instance, $old_instance ) {
$instance = array();
$instance['baslik'] = sanitize_text_field( $new_instance['baslik'] );
$instance['adet'] = absint( $new_instance['adet'] );
return $instance;
}
public function form( $instance ) {
$baslik = isset( $instance['baslik'] ) ? $instance['baslik'] : 'İlgili Yazılar';
$adet = isset( $instance['adet'] ) ? intval( $instance['adet'] ) : 5;
?>
<p>
<label>Başlık:</label>
<input class="widefat" name="<?php echo $this->get_field_name( 'baslik' ); ?>"
type="text" value="<?php echo esc_attr( $baslik ); ?>">
</p>
<p>
<label>Yazı Sayısı:</label>
<input class="widefat" name="<?php echo $this->get_field_name( 'adet' ); ?>"
type="number" min="1" max="10" value="<?php echo esc_attr( $adet ); ?>">
</p>
<?php
}
}
function sysadmin_widget_kayit() {
register_widget( 'SysAdmin_Ilgili_Yazilar_Widget' );
}
add_action( 'widgets_init', 'sysadmin_widget_kayit' );
the_content Hook ile Otomatik Ekleme
Her single.php dosyasını manuel düzenlemek yerine the_content filtresine takılarak içeriğin altına otomatik ekleyebilirsiniz. Tema değişince de kaybolmaz.
// functions.php
function sysadmin_icerige_ilgili_ekle( $icerik ) {
// Sadece yayınlanmış tekil yazılarda çalış
if ( ! is_single() || ! in_the_loop() || ! is_main_query() ) {
return $icerik;
}
$post_id = get_the_ID();
$yazilar = sysadmin_ilgili_yazilar_cached( $post_id, 4 );
if ( empty( $yazilar ) ) {
return $icerik;
}
$html = '<div class="ilgili-yazilar-kutu" style="margin-top:2em;padding:1.5em;background:#f9f9f9;border-left:4px solid #0073aa;">';
$html .= '<h4 style="margin-top:0;color:#0073aa;">📌 Bunları Da Okuyun</h4>';
$html .= '<ul style="margin:0;padding-left:1.2em;">';
foreach ( $yazilar as $yazi ) {
$yorum_sayisi = get_comments_number( $yazi->ID );
$tarih = get_the_date( 'd M Y', $yazi->ID );
$html .= '<li style="margin-bottom:.5em;">';
$html .= '<a href="' . get_permalink( $yazi->ID ) . '" rel="bookmark">';
$html .= esc_html( $yazi->post_title );
$html .= '</a>';
$html .= ' <small style="color:#999;">(' . $tarih . ')</small>';
$html .= '</li>';
}
$html .= '</ul></div>';
return $icerik . $html;
}
add_filter( 'the_content', 'sysadmin_icerige_ilgili_ekle' );
Bu yaklaşımın en büyük avantajı tema bağımsızlığı. Yarın temayı değiştirseniz bile ilgili yazılar bölümü çalışmaya devam eder çünkü functions.php üzerinde ya da daha iyisi bir site-specific eklenti üzerinde duruyor.
Özel Taksonomi Desteği
WooCommerce kullananlar veya özel post type’larla çalışanlar için taksonomiye göre ilgili içerik çekme fonksiyonu farklı yapılmalı.
// functions.php - Özel taksonomi desteğiyle
function sysadmin_taksonomiye_gore_ilgili( $post_id, $taksonomi = 'category', $adet = 5 ) {
$terimler = wp_get_object_terms( $post_id, $taksonomi, array( 'fields' => 'ids' ) );
if ( is_wp_error( $terimler ) || empty( $terimler ) ) {
return array();
}
$post_type = get_post_type( $post_id );
$args = array(
'post_type' => $post_type,
'post_status' => 'publish',
'post__not_in' => array( $post_id ),
'posts_per_page' => $adet,
'tax_query' => array(
array(
'taxonomy' => $taksonomi,
'field' => 'term_id',
'terms' => $terimler,
'operator' => 'IN',
),
),
'orderby' => 'date',
'order' => 'DESC',
'no_found_rows' => true, // Sayfalama gereksiz, performans artar
'update_post_meta_cache' => false, // Meta cache'i atla
'update_post_term_cache' => false, // Term cache'i atla
);
$sorgu = new WP_Query( $args );
$yazilar = $sorgu->posts;
wp_reset_postdata();
return $yazilar;
}
// WooCommerce ürünleri için kullanım örneği:
// sysadmin_taksonomiye_gore_ilgili( $product_id, 'product_cat', 4 );
// Özel post type için kullanım örneği:
// sysadmin_taksonomiye_gore_ilgili( $film_id, 'film_turu', 6 );
no_found_rows => true ve cache devre dışı bırakma parametrelerine dikkat edin. İlgili yazı sorgularında kaçıncı sayfada olduğunuzu bilmenize gerek yok, bu sayımı atlamak sorguyu belirgin ölçüde hızlandırıyor.
Sonuç
functions.php üzerinden ilgili yazılar sistemi kurmak, hazır eklentiye kıyasla başlangıçta biraz daha fazla kod yazmak anlamına geliyor ama kazanımlar net:
- Performans: Gereksiz tablo sorgusu, eklenti yükü veya HTTP isteği yok
- Esneklik: Kategori, etiket, taksonomi veya özel post type fark etmiyor, her yapıya uyum sağlıyor
- Kontrol: Kaç yazı, hangi sıralama, hangi görünüm, nerede gösterilsin, hepsi sizin elinizde
- Tema bağımsızlığı:
the_contenthookuna bağlandığınızda tema değişince sıfırdan başlamıyorsunuz - Önbellek: Transient ile veritabanı yükünü minimuma indirebiliyorsunuz
Başlangıç için en pratik yol: the_content hook yaklaşımıyla temel kategori tabanlı fonksiyonu birleştirmek. Site büyüdükçe transient cache ekleyin, trafik yoğunlaştıkça no_found_rows ve meta cache optimizasyonlarına geçin. Sysadmin mantığının aynısı burada da geçerli: önce çalışır hale getir, sonra ölç, sonra optimize et.
