Article Schema ile Blog Yazılarını Zengin Sonuçlara Taşıma
Google’da bir blog yazısı aradığınızda bazı sonuçların yanında yazar fotoğrafı, yayın tarihi, okuma süresi gibi bilgiler görürsünüz. İşte bu “zengin sonuçlar” (rich results) arkasında Article Schema adı verilen yapısal veri formatı yatıyor. WordPress sitenize doğru schema markup eklemek, arama motorlarına içeriğinizi daha iyi anlatmanızı sağlar ve SERP’teki görünürlüğünüzü ciddi ölçüde artırır. Bu yazıda functions.php üzerinden Article Schema’yı nasıl ekleyeceğinizi, özelleştireceğinizi ve test edeceğinizi adım adım göreceğiz.
Article Schema Nedir ve Neden Önemlidir
Schema.org tarafından tanımlanan Article yapısal verisi, bir sayfanın makale, blog yazısı veya haber içeriği olduğunu arama motorlarına bildirir. Google bu veriyi kullandığında arama sonuçlarınız standart mavi link görünümünün ötesine geçer. Yazar bilgisi, yayın tarihi, güncellenme tarihi, öne çıkan görsel gibi bilgiler doğrudan SERP üzerinde gösterilebilir.
Sysadmin perspektifinden baktığınızda bu bir “metadata yönetimi” meselesidir. Sunucunuzu ne kadar iyi yapılandırırsanız o kadar verimli çalışır, sayfalarınızı ne kadar iyi etiketlerseniz Google o kadar iyi anlayıp öne çıkarır. Ek bir eklenti kurmak yerine functions.php üzerinden bu işi halletmek hem daha hafif hem de daha kontrollü bir yaklaşım sunar.
WordPress functions.php’ye Neden Güvenmeliyiz
Birçok site sahibi schema için Yoast SEO veya Rank Math gibi eklentileri tercih eder. Bu eklentiler güçlüdür ama beraberinde yüzlerce kiloluk CSS, JS ve veritabanı sorgusu getirir. Eğer sade bir tema kullanıyorsanız veya hafif bir SEO altyapısı kuruyorsanız, schema markup’ı doğrudan functions.php içinde PHP ile üretmek çok daha mantıklıdır.
Kontrol sizde olur, çakışma riski düşer, siteniz hızlı kalır.
Temel Article Schema Yapısı
Önce en sade haliyle bir Article schema çıktısına bakalım. Bu kod bloğu etiketi içine JSON-LD formatında bir etiketi ekler.
// functions.php - Temel Article Schema
function mf_article_schema() {
if ( ! is_single() ) return;
global $post;
$author = get_the_author_meta( 'display_name', $post->post_author );
$date_pub = get_the_date( 'c', $post );
$date_mod = get_the_modified_date( 'c', $post );
$title = get_the_title( $post );
$url = get_permalink( $post );
$schema = [
'@context' => 'https://schema.org',
'@type' => 'Article',
'headline' => $title,
'author' => [
'@type' => 'Person',
'name' => $author,
],
'datePublished' => $date_pub,
'dateModified' => $date_mod,
'url' => $url,
];
echo '<script type="application/ld+json">'
. wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES )
. '</script>' . "n";
}
add_action( 'wp_head', 'mf_article_schema' );
Bu kodu kaydedip Google’ın Rich Results Test aracına sayfanızın URL’sini yapıştırdığınızda Article tipini tanıdığını göreceksiniz. Ama bu sadece başlangıç.
Öne Çıkan Görsel Ekleme
Google, Article schema’da image alanını özellikle önemsiyor. Öne çıkan görsel yoksa zengin sonuç gösterim şansınız düşer. Şimdi bu alanı da ekleyelim.
// functions.php - Öne çıkan görsel ile Article Schema
function mf_article_schema() {
if ( ! is_single() ) return;
global $post;
setup_postdata( $post );
$author = get_the_author_meta( 'display_name', $post->post_author );
$date_pub = get_the_date( 'c', $post );
$date_mod = get_the_modified_date( 'c', $post );
$title = get_the_title( $post );
$url = get_permalink( $post );
$desc = has_excerpt( $post )
? strip_tags( get_the_excerpt( $post ) )
: wp_trim_words( strip_tags( $post->post_content ), 30 );
$image_data = [];
if ( has_post_thumbnail( $post ) ) {
$thumb = wp_get_attachment_image_src(
get_post_thumbnail_id( $post ),
'full'
);
if ( $thumb ) {
$image_data = [
'@type' => 'ImageObject',
'url' => $thumb[0],
'width' => $thumb[1],
'height' => $thumb[2],
];
}
}
$schema = [
'@context' => 'https://schema.org',
'@type' => 'Article',
'headline' => $title,
'description' => $desc,
'author' => [
'@type' => 'Person',
'name' => $author,
],
'datePublished' => $date_pub,
'dateModified' => $date_mod,
'url' => $url,
];
if ( ! empty( $image_data ) ) {
$schema['image'] = $image_data;
}
echo '<script type="application/ld+json">'
. wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES )
. '</script>' . "n";
}
add_action( 'wp_head', 'mf_article_schema' );
Publisher (Yayıncı) Bilgisi Eklemek
Google’ın rehberleri publisher alanını da içermesi gerektiğini söylüyor. Organizasyon adı ve logosu buraya girer. Bunu site genelinde bir sabit olarak tanımlamak en temiz yaklaşım.
// functions.php - Publisher bloğu ile genişletilmiş schema
function mf_get_publisher_block() {
$logo_id = get_theme_mod( 'custom_logo' );
$logo_url = '';
$logo_w = 0;
$logo_h = 0;
if ( $logo_id ) {
$logo_src = wp_get_attachment_image_src( $logo_id, 'full' );
if ( $logo_src ) {
$logo_url = $logo_src[0];
$logo_w = $logo_src[1];
$logo_h = $logo_src[2];
}
}
$publisher = [
'@type' => 'Organization',
'name' => get_bloginfo( 'name' ),
'url' => home_url(),
];
if ( $logo_url ) {
$publisher['logo'] = [
'@type' => 'ImageObject',
'url' => $logo_url,
'width' => $logo_w,
'height' => $logo_h,
];
}
return $publisher;
}
Bu yardımcı fonksiyonu ana schema fonksiyonunuzda çağırırsınız:
// Ana schema array'ine publisher eklemek
$schema['publisher'] = mf_get_publisher_block();
Kategori ve Etiket Bilgilerini Schema’ya Dahil Etmek
Gerçek dünya senaryolarında bir sysadmin blogunun kategorileri “Linux”, “Windows Server”, “Güvenlik”, “Otomasyon” gibi başlıklar içerir. Bu bilgiyi articleSection ve keywords alanlarıyla schema’ya eklemek içeriğinizin konusal ilişkilerini pekiştirir.
// functions.php - Kategori ve etiket entegrasyonu
function mf_get_article_keywords( $post_id ) {
$terms = [];
$cats = get_the_category( $post_id );
$tags = get_the_tags( $post_id );
if ( $cats && ! is_wp_error( $cats ) ) {
foreach ( $cats as $cat ) {
$terms[] = $cat->name;
}
}
if ( $tags && ! is_wp_error( $tags ) ) {
foreach ( $tags as $tag ) {
$terms[] = $tag->name;
}
}
return implode( ', ', array_unique( $terms ) );
}
function mf_get_article_section( $post_id ) {
$cats = get_the_category( $post_id );
if ( $cats && ! is_wp_error( $cats ) && isset( $cats[0] ) ) {
return $cats[0]->name;
}
return '';
}
Ana fonksiyonunuza şu satırları ekleyerek bu verileri schema’ya bağlarsınız:
$keywords = mf_get_article_keywords( $post->ID );
$section = mf_get_article_section( $post->ID );
if ( $keywords ) {
$schema['keywords'] = $keywords;
}
if ( $section ) {
$schema['articleSection'] = $section;
}
BlogPosting ve NewsArticle Türleri Arasında Dinamik Seçim
Article genel bir tür. Google BlogPosting ve NewsArticle türlerini de destekliyor. Bir sysadmin blogunda genellikle BlogPosting daha uygun, ama bir “duyuru” veya “güvenlik açıklaması” kategorisindeyseniz NewsArticle daha isabetli olur. Bunu kategori bazlı otomatik seçimle çözebilirsiniz.
// functions.php - Dinamik @type seçimi
function mf_get_article_type( $post_id ) {
$news_cats = [ 'duyurular', 'guvenlik-aciklamalari', 'haberler' ];
$cats = get_the_category( $post_id );
if ( $cats && ! is_wp_error( $cats ) ) {
foreach ( $cats as $cat ) {
if ( in_array( $cat->slug, $news_cats, true ) ) {
return 'NewsArticle';
}
}
}
return 'BlogPosting';
}
$schema['@type'] değerini bu fonksiyonla dinamik olarak belirlersiniz:
$schema['@type'] = mf_get_article_type( $post->ID );
Okuma Süresi Hesaplama ve wordCount Alanı
Google’ın Article schema belgelerinde wordCount alanı destekleniyor. Ortalama okuma süresi ise bazı temalarda gösterilir ama schema’ya da eklenebilir. Her ikisini birden hesaplayalım.
// functions.php - Kelime sayısı ve okuma süresi
function mf_get_word_count( $content ) {
$clean = strip_tags( $content );
$clean = preg_replace( '/s+/', ' ', $clean );
return str_word_count( trim( $clean ) );
}
function mf_get_reading_time( $content ) {
$word_count = mf_get_word_count( $content );
$words_per_min = 200; // Ortalama Turkce okuma hizi
$minutes = (int) ceil( $word_count / $words_per_min );
return $minutes . ' dakika';
}
Bu iki değeri schema’ya şu şekilde eklersiniz:
$word_count = mf_get_word_count( $post->post_content );
if ( $word_count > 0 ) {
$schema['wordCount'] = $word_count;
}
Her Şeyi Bir Araya Getiren Tam Fonksiyon
Yukarıdaki tüm parçaları birleştirerek production-ready, modüler bir schema fonksiyonu oluşturalım. Bu fonksiyon hem single post hem de custom post type destekliyor.
// functions.php - Tam Article Schema implementasyonu
function mf_output_article_schema() {
if ( ! is_singular( [ 'post', 'rehber', 'tutorial' ] ) ) {
return;
}
global $post;
setup_postdata( $post );
// Temel veriler
$author_id = $post->post_author;
$author = [
'@type' => 'Person',
'name' => get_the_author_meta( 'display_name', $author_id ),
'url' => get_author_posts_url( $author_id ),
];
// Yazar sosyal profili varsa ekle
$twitter = get_the_author_meta( 'twitter', $author_id );
if ( $twitter ) {
$author['sameAs'] = 'https://twitter.com/' . esc_attr( $twitter );
}
// Icеrik bilgileri
$title = wp_strip_all_tags( get_the_title( $post ) );
$url = get_permalink( $post );
$date_pub = get_the_date( 'c', $post );
$date_mod = get_the_modified_date( 'c', $post );
// Aciklama
$desc = has_excerpt( $post )
? wp_strip_all_tags( get_the_excerpt( $post ) )
: wp_trim_words( wp_strip_all_tags( $post->post_content ), 40 );
// Schema array baslatma
$schema = [
'@context' => 'https://schema.org',
'@type' => mf_get_article_type( $post->ID ),
'mainEntityOfPage' => [
'@type' => 'WebPage',
'@id' => $url,
],
'headline' => $title,
'description' => $desc,
'author' => $author,
'publisher' => mf_get_publisher_block(),
'datePublished' => $date_pub,
'dateModified' => $date_mod,
'url' => $url,
'wordCount' => mf_get_word_count( $post->post_content ),
'inLanguage' => 'tr-TR',
];
// One cikan gorsel
if ( has_post_thumbnail( $post ) ) {
$thumb = wp_get_attachment_image_src(
get_post_thumbnail_id( $post ),
'full'
);
if ( $thumb ) {
$schema['image'] = [
'@type' => 'ImageObject',
'url' => $thumb[0],
'width' => $thumb[1],
'height' => $thumb[2],
];
}
}
// Kategori ve etiketler
$keywords = mf_get_article_keywords( $post->ID );
$section = mf_get_article_section( $post->ID );
if ( $keywords ) {
$schema['keywords'] = $keywords;
}
if ( $section ) {
$schema['articleSection'] = $section;
}
// Cikti
echo '<script type="application/ld+json">'
. wp_json_encode(
$schema,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT
)
. '</script>' . "n";
wp_reset_postdata();
}
add_action( 'wp_head', 'mf_output_article_schema' );
Schema Çıktısını Test Etmek
Kodu ekledikten sonra doğrulamanız şart. İşte kullanabileceğiniz araçlar ve adımlar:
- Google Rich Results Test: https://search.google.com/test/rich-results adresine sitenizin bir yazı URL’sini yapıştırın. “Article” kartını bulun ve hataları inceleyin.
- Schema.org Validator: https://validator.schema.org adresinde ham JSON-LD çıktınızı test edebilirsiniz.
- Browser DevTools: Sayfa kaynağını açın,
application/ld+jsonaramasıyla schema bloğunu bulup JSON formatını kontrol edin. - Google Search Console: “Zengin sonuçlar” raporunu takip edin. Sayfanız indekslendikten sonra burada hata veya uyarı çıkıyorsa düzeltin.
Curl ile de hızlıca kontrol edebilirsiniz:
# Sayfa kaynağından schema bloğunu cekmek
curl -s "https://sizinsiteniz.com/ornek-yazi/"
| grep -o '<script type="application/ld+json">.*</script>'
| sed 's/<script type="application/ld+json">//;s/</script>//'
Sık Karşılaşılan Hatalar ve Çözümleri
Gerçek projelerde karşılaştığım yaygın sorunları ve çözümlerini maddeler halinde paylaşayım:
- “headline alanı 110 karakterden uzun”: Google bu sınırı uyarı olarak gösteriyor.
$titledeğişkeninimb_substr( $title, 0, 110 )ile kırpabilirsiniz. Ancak başlıklarınızı doğal olarak kısa tutmak daha iyi bir çözüm. - “image alanı eksik”: Öne çıkan görsel seçilmemiş yazılarda bu uyarı gelir. Ya her yazıya öne çıkan görsel eklemek için editoryal bir kural koyun ya da fallback olarak site logosunu kullanın.
- JSON encoding hatası: Türkçe karakterler veya özel karakterler zaman zaman JSON’u bozar.
wp_json_encodeyerine nativejson_encodekullanmayın, WordPress’in kendi fonksiyonu güvenli encoding sağlar. - Schema çakışması: Başka bir eklenti de schema ekliyorsa iki ayrı
bloğu oluşur. Bu teknik hata değil ama kafa karıştırıcı. Yoast veya Rank Math’in schema çıktısını kapatmak için ilgili eklenti ayarlarına gidin. - Custom post type’larda çalışmıyor:
is_single()yalnızca standart post için çalışır. Özel post tiplerini dahil etmek içinis_singular( ['post', 'tutorial'] )kullanın. - Çıktı tamponlama sorunu: Bazı temalar
wp_headhook’unu doğru çalıştırmaz. Temayıwp_head()çağırıp çağırmadığını kontrol edin.
Performans Açısından Değerlendirme
Schema fonksiyonunuz her sayfa yüklemesinde çalışır. Veritabanı sorguları minimize edilmeli. Şu önlemleri alın:
get_the_author_meta,get_the_categorygibi fonksiyonlar WordPress’in object cache’ini kullanır. Ekstra sorgu açmaz.- Logo URL’sini her seferinde hesaplamak yerine transient’a alabilirsiniz. Özellikle
wp_get_attachment_image_srcbiraz maliyetlidir. - Schema fonksiyonunu yalnızca ihtiyaç duyulan sayfa tiplerine kısıtlayın.
is_singularkontrolü bu yüzden kritik.
// Logo URL'sini transient ile cache'leme
function mf_get_logo_url_cached() {
$cache_key = 'mf_site_logo_data';
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
$logo_id = get_theme_mod( 'custom_logo' );
$logo_src = $logo_id ? wp_get_attachment_image_src( $logo_id, 'full' ) : false;
$data = $logo_src ? [
'url' => $logo_src[0],
'width' => $logo_src[1],
'height' => $logo_src[2],
] : [];
set_transient( $cache_key, $data, DAY_IN_SECONDS );
return $data;
}
// Logo degistiginde transient'i temizle
add_action( 'update_option_theme_mods_' . get_option( 'stylesheet' ), function() {
delete_transient( 'mf_site_logo_data' );
} );
Çoklu Yazar Senaryoları
Birden fazla yazarın katkıda bulunduğu sysadmin bloglarında yazar bilgilerini zenginleştirmek önemli. WordPress kullanıcı profillerine LinkedIn veya GitHub alanı ekleyerek bunu schema’ya yansıtabilirsiniz.
// functions.php - Genisletilmis yazar blogu
function mf_get_rich_author_block( $author_id ) {
$author = [
'@type' => 'Person',
'name' => get_the_author_meta( 'display_name', $author_id ),
'url' => get_author_posts_url( $author_id ),
];
$same_as = [];
$github = get_the_author_meta( 'github', $author_id );
$linkedin = get_the_author_meta( 'linkedin', $author_id );
$twitter = get_the_author_meta( 'twitter', $author_id );
if ( $github ) $same_as[] = 'https://github.com/' . $github;
if ( $linkedin ) $same_as[] = 'https://linkedin.com/in/' . $linkedin;
if ( $twitter ) $same_as[] = 'https://twitter.com/' . $twitter;
if ( ! empty( $same_as ) ) {
$author['sameAs'] = $same_as;
}
$bio = get_the_author_meta( 'description', $author_id );
if ( $bio ) {
$author['description'] = wp_strip_all_tags( $bio );
}
return $author;
}
Bu fonksiyonu yazar profillerinde kullanıcı alanlarıyla birleştirmek için ek bir user_contactmethods hook’u yazabilirsiniz. Böylece kullanıcı profil sayfasında GitHub ve LinkedIn alanları görünür.
Sonuç
Article Schema, WordPress sitenize functions.php üzerinden eklediğinizde hem hafif hem de tamamen özelleştirilebilir bir yapı sunar. Eklenti bağımlılığı olmadan, sitenizin tam kontrolünde bir zengin sonuç altyapısı kurmuş olursunuz. Anlatılan yaklaşımda temel schema ile başladık, öne çıkan görsel, publisher bloğu, dinamik tür seçimi, kelime sayısı ve yazar profil zenginleştirmeyle genişlettik.
Gerçek faydayı görmek için sabırlı olmak gerekiyor. Google sayfanızı yeniden tarayıp schema’yı işleyene kadar birkaç gün geçebilir. Search Console’u düzenli takip edin, zengin sonuç raporunda yeşil onaylar görmeye başladığınızda tıklama oranlarınızın da pozitif etkilendiğini fark edeceksiniz.
Kodu production’a almadan önce bir staging ortamında test edin, Rich Results Test aracından geçirin ve mevcut SEO eklentilerinizle çakışma olup olmadığını kontrol edin. Bu üç adımı tamamladıktan sonra deploy etmek güvenlidir.
