WordPress Yazılarına Otomatik Schema Markup Ekleme: JSON-LD

Google’da üst sıralarda yer almak istiyorsanız, iyi içerik yazmak tek başına yeterli değil. Arama motorlarının içeriğinizi doğru anlaması ve zengin sonuçlar (rich results) olarak göstermesi için Schema Markup kullanmanız gerekiyor. WordPress’te bu işi manuel yapmak hem zahmetli hem de hata yapmaya açık. İşte tam bu noktada functions.php devreye giriyor: birkaç fonksiyonla tüm yazılarınıza otomatik JSON-LD Schema ekleyebilirsiniz.

Schema Markup Nedir ve Neden Önemlidir

Schema Markup, arama motorlarına içeriğinizin ne hakkında olduğunu söyleyen yapılandırılmış veri formatıdır. Schema.org tarafından tanımlanan bu standart, Google, Bing ve Yandex tarafından destekleniyor.

JSON-LD (JavaScript Object Notation for Linked Data) ise bu yapılandırılmış veriyi HTML’e gömmeden, etiketi içinde ayrı bir blok olarak tanımlamanıza izin veriyor. Google, JSON-LD formatını HTML içine yerleştirilmiş Microdata veya RDFa’ya kıyasla çok daha kolay işleyebiliyor ve açıkça öneriyor.

Peki bu ne işe yarıyor? Bir blog yazısına Article Schema eklendiğinde Google şunları anlıyor:

  • Yazarın kim olduğunu ve bu kişinin güvenilir bir kaynak mı olduğunu
  • Yayın tarihini ve güncellenme tarihini doğru biçimde
  • Başlığı ve açıklamayı doğrudan içerikten
  • Öne çıkan görseli ve telif hakkı bilgisini

Bu bilgiler sayesinde arama sonuçlarında yazar fotoğrafı, tarih ve derecelendirme gibi ekstra görsel öğeler çıkabiliyor. Bu da tıklama oranını ciddi artırıyor.

Temel Kurulum: Article Schema Ekleme

Başlangıç olarak en temel ve en etkili Schema türüyle başlayalım: Article. Bu schema, blog yazıları ve haber içerikleri için standart kabul ediliyor.

function wpblog_add_article_schema() {
    if ( ! is_single() ) {
        return;
    }

    global $post;

    $author_id   = $post->post_author;
    $author_name = get_the_author_meta( 'display_name', $author_id );
    $author_url  = get_author_posts_url( $author_id );

    $thumbnail_url = '';
    if ( has_post_thumbnail( $post->ID ) ) {
        $thumbnail = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'full' );
        $thumbnail_url = $thumbnail ? $thumbnail[0] : '';
    }

    $schema = array(
        '@context'         => 'https://schema.org',
        '@type'            => 'Article',
        'headline'         => get_the_title( $post->ID ),
        'description'      => wp_strip_all_tags( get_the_excerpt( $post->ID ) ),
        'datePublished'    => get_the_date( 'c', $post->ID ),
        'dateModified'     => get_the_modified_date( 'c', $post->ID ),
        'author'           => array(
            '@type' => 'Person',
            'name'  => $author_name,
            'url'   => $author_url,
        ),
        'publisher'        => array(
            '@type' => 'Organization',
            'name'  => get_bloginfo( 'name' ),
            'url'   => home_url(),
        ),
        'mainEntityOfPage' => array(
            '@type' => '@id',
            'url'   => get_permalink( $post->ID ),
        ),
    );

    if ( $thumbnail_url ) {
        $schema['image'] = array(
            '@type' => 'ImageObject',
            'url'   => $thumbnail_url,
        );
    }

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

Bu kodu functions.php dosyanıza ekleyin. wp_head hook’u sayesinde schema her yazı sayfasının bölümüne otomatik ekleniyor. is_single() kontrolü ile sadece tekil yazı sayfalarında çalışmasını sağladık, kategori veya ana sayfa gibi listeleme sayfaları etkilenmiyor.

Farklı Post Type’lar için Schema Tipi Belirleme

Tek tip schema yeterli değil. Bir “nasıl yapılır” yazısı ile normal bir makale farklı schema tiplerini hak ediyor. Aşağıdaki fonksiyon, kategoriye veya etiketlere göre doğru schema tipini seçiyor:

function wpblog_get_schema_type_by_category( $post_id ) {
    $categories = get_the_category( $post_id );
    $tags       = get_the_tags( $post_id );

    $category_slugs = array();
    if ( $categories ) {
        foreach ( $categories as $cat ) {
            $category_slugs[] = $cat->slug;
        }
    }

    $tag_slugs = array();
    if ( $tags ) {
        foreach ( $tags as $tag ) {
            $tag_slugs[] = $tag->slug;
        }
    }

    // Kategori slug'larına gore schema tipi belirle
    if ( in_array( 'nasil-yapilir', $category_slugs ) || in_array( 'how-to', $category_slugs ) ) {
        return 'HowTo';
    }

    if ( in_array( 'haberler', $category_slugs ) || in_array( 'news', $category_slugs ) ) {
        return 'NewsArticle';
    }

    if ( in_array( 'inceleme', $category_slugs ) || in_array( 'review', $category_slugs ) ) {
        return 'Review';
    }

    // Etiketlere gore kontrol
    if ( in_array( 'rehber', $tag_slugs ) || in_array( 'guide', $tag_slugs ) ) {
        return 'TechArticle';
    }

    return 'Article';
}

Bu yardımcı fonksiyonu bir önceki fonksiyonla birleştirdiğinizde, siteminizdeki her yazı kendi kategorisine uygun schema tipini otomatik olarak alıyor. Bunu ana schema fonksiyonuna şöyle entegre edebilirsiniz:

// Ana schema fonksiyonunda su satiri degistirin:
// '@type' => 'Article',
// Bununla degistirin:
'@type' => wpblog_get_schema_type_by_category( $post->ID ),

BreadcrumbList Schema Ekleme

Breadcrumb (ekmek kırıntısı) navigasyonu hem kullanıcı deneyimi hem de SEO açısından kritik. Google, BreadcrumbList schema’sını arama sonuçlarında URL yerine kategori yolunu göstermek için kullanıyor. Bu da arama sonucunun çok daha profesyonel görünmesini sağlıyor.

function wpblog_add_breadcrumb_schema() {
    if ( ! is_single() && ! is_page() ) {
        return;
    }

    global $post;

    $breadcrumbs    = array();
    $position       = 1;

    // Ana sayfa her zaman ilk item
    $breadcrumbs[] = array(
        '@type'    => 'ListItem',
        'position' => $position,
        'name'     => get_bloginfo( 'name' ),
        'item'     => home_url(),
    );
    $position++;

    // Kategoriler
    $categories = get_the_category( $post->ID );
    if ( $categories && ! empty( $categories ) ) {
        $main_cat = $categories[0];

        // Ust kategori varsa onu da ekle
        if ( $main_cat->parent ) {
            $parent_cat = get_category( $main_cat->parent );
            $breadcrumbs[] = array(
                '@type'    => 'ListItem',
                'position' => $position,
                'name'     => $parent_cat->name,
                'item'     => get_category_link( $parent_cat->term_id ),
            );
            $position++;
        }

        $breadcrumbs[] = array(
            '@type'    => 'ListItem',
            'position' => $position,
            'name'     => $main_cat->name,
            'item'     => get_category_link( $main_cat->term_id ),
        );
        $position++;
    }

    // Mevcut sayfa/yazi
    $breadcrumbs[] = array(
        '@type'    => 'ListItem',
        'position' => $position,
        'name'     => get_the_title( $post->ID ),
        'item'     => get_permalink( $post->ID ),
    );

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

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

FAQ Schema: Sık Sorulan Sorular için

Eğer yazılarınızda soru-cevap bölümleri bulunuyorsa, FAQ Schema ile bu bölümleri doğrudan arama sonuçlarında gösterebilirsiniz. Bu özellik özellikle “insanlar şunu da soruyor” bloklarında görünmenizi sağlıyor.

Bu işlem için bir custom field mantığı kuralım. Yazı düzenleme ekranına FAQ eklemek için meta box kullanalım:

// FAQ Meta Box olustur
function wpblog_add_faq_meta_box() {
    add_meta_box(
        'wpblog_faq_schema',
        'FAQ Schema (JSON-LD)',
        'wpblog_faq_meta_box_callback',
        'post',
        'normal',
        'high'
    );
}
add_action( 'add_meta_boxes', 'wpblog_add_faq_meta_box' );

function wpblog_faq_meta_box_callback( $post ) {
    $faq_data = get_post_meta( $post->ID, '_wpblog_faq_data', true );
    wp_nonce_field( 'wpblog_faq_nonce', 'wpblog_faq_nonce_field' );
    ?>
    <p><strong>JSON formatinda FAQ girin. Ornek:</strong></p>
    <pre>[{"question":"Soru 1","answer":"Cevap 1"},{"question":"Soru 2","answer":"Cevap 2"}]</pre>
    <textarea name="wpblog_faq_data" rows="8" style="width:100%"><?php echo esc_textarea( $faq_data ); ?></textarea>
    <?php
}

// Meta box verisini kaydet
function wpblog_save_faq_meta( $post_id ) {
    if ( ! isset( $_POST['wpblog_faq_nonce_field'] ) ) {
        return;
    }
    if ( ! wp_verify_nonce( $_POST['wpblog_faq_nonce_field'], 'wpblog_faq_nonce' ) ) {
        return;
    }
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    if ( isset( $_POST['wpblog_faq_data'] ) ) {
        update_post_meta( $post_id, '_wpblog_faq_data', sanitize_textarea_field( $_POST['wpblog_faq_data'] ) );
    }
}
add_action( 'save_post', 'wpblog_save_faq_meta' );

// FAQ Schema'yi <head>'e ekle
function wpblog_add_faq_schema() {
    if ( ! is_single() ) {
        return;
    }

    global $post;

    $faq_raw = get_post_meta( $post->ID, '_wpblog_faq_data', true );
    if ( empty( $faq_raw ) ) {
        return;
    }

    $faq_items = json_decode( $faq_raw, true );
    if ( ! is_array( $faq_items ) || empty( $faq_items ) ) {
        return;
    }

    $entities = array();
    foreach ( $faq_items as $item ) {
        if ( empty( $item['question'] ) || empty( $item['answer'] ) ) {
            continue;
        }
        $entities[] = array(
            '@type'          => 'Question',
            'name'           => sanitize_text_field( $item['question'] ),
            'acceptedAnswer' => array(
                '@type' => 'Answer',
                'text'  => wp_kses_post( $item['answer'] ),
            ),
        );
    }

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

    $schema = array(
        '@context'   => 'https://schema.org',
        '@type'      => 'FAQPage',
        'mainEntity' => $entities,
    );

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

Yazar Profili Schema: Person ve Author

Özellikle çok yazarlı bloglarda her yazar için ayrı Person schema oluşturmak, E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness) sinyallerini güçlendiriyor. Yazar sayfalarında bu schema otomatik çıksın:

function wpblog_add_author_schema() {
    if ( ! is_author() ) {
        return;
    }

    $author_id       = get_queried_object_id();
    $author_name     = get_the_author_meta( 'display_name', $author_id );
    $author_bio      = get_the_author_meta( 'description', $author_id );
    $author_url      = get_author_posts_url( $author_id );
    $author_email    = get_the_author_meta( 'user_email', $author_id );
    $author_twitter  = get_the_author_meta( 'twitter', $author_id );
    $author_linkedin = get_the_author_meta( 'linkedin', $author_id );

    // Gravatar URL'si
    $gravatar_url = get_avatar_url( $author_email, array( 'size' => 400 ) );

    $same_as = array();
    if ( $author_twitter ) {
        $same_as[] = 'https://twitter.com/' . ltrim( $author_twitter, '@' );
    }
    if ( $author_linkedin ) {
        $same_as[] = $author_linkedin;
    }

    $schema = array(
        '@context'   => 'https://schema.org',
        '@type'      => 'Person',
        'name'       => $author_name,
        'url'        => $author_url,
        'description' => $author_bio,
        'image'      => array(
            '@type' => 'ImageObject',
            'url'   => $gravatar_url,
        ),
        'worksFor'   => array(
            '@type' => 'Organization',
            'name'  => get_bloginfo( 'name' ),
            'url'   => home_url(),
        ),
    );

    if ( ! empty( $same_as ) ) {
        $schema['sameAs'] = $same_as;
    }

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

Bu fonksiyon kullanıcı profil sayfasındaki Twitter ve LinkedIn alanlarını okuyarak sameAs özelliğini dolduruyor. Bu, Google’ın yazarı diğer platformlardaki profilleriyle ilişkilendirmesine yardımcı oluyor.

Organization ve WebSite Schema: Site Geneli

Ana sayfaya özel Organization ve WebSite schema eklemek, sitenizin genel varlığını (entity) Google’a tanıtıyor. Arama sonuçlarında sağ tarafta çıkan Knowledge Panel bu verilerden beslenebiliyor:

function wpblog_add_site_schema() {
    if ( ! is_front_page() ) {
        return;
    }

    $site_name        = get_bloginfo( 'name' );
    $site_url         = home_url();
    $site_description = get_bloginfo( 'description' );

    // Site logosunu options'tan al (WordPress Customizer logo)
    $logo_id  = get_theme_mod( 'custom_logo' );
    $logo_url = '';
    if ( $logo_id ) {
        $logo_data = wp_get_attachment_image_src( $logo_id, 'full' );
        $logo_url  = $logo_data ? $logo_data[0] : '';
    }

    // Organization schema
    $org_schema = array(
        '@context'    => 'https://schema.org',
        '@type'       => 'Organization',
        '@id'         => $site_url . '/#organization',
        'name'        => $site_name,
        'url'         => $site_url,
        'description' => $site_description,
    );

    if ( $logo_url ) {
        $org_schema['logo'] = array(
            '@type' => 'ImageObject',
            'url'   => $logo_url,
        );
    }

    // WebSite schema (Sitelinks searchbox icin)
    $website_schema = array(
        '@context'        => 'https://schema.org',
        '@type'           => 'WebSite',
        '@id'             => $site_url . '/#website',
        'name'            => $site_name,
        'url'             => $site_url,
        'potentialAction' => array(
            '@type'       => 'SearchAction',
            'target'      => array(
                '@type'       => 'EntryPoint',
                'urlTemplate' => $site_url . '/?s={search_term_string}',
            ),
            'query-input' => 'required name=search_term_string',
        ),
    );

    echo '<script type="application/ld+json">' . wp_json_encode( $org_schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ) . '</script>';
    echo '<script type="application/ld+json">' . wp_json_encode( $website_schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ) . '</script>';
}
add_action( 'wp_head', 'wpblog_add_site_schema' );

SearchAction kısmı, Google’ın arama sonuçlarında sitenize özel bir arama kutusu (Sitelinks Searchbox) göstermesini sağlayabiliyor. Bu özellik özellikle büyük siteler için çok değerli.

Schema Doğrulama ve Hata Ayıklama

Eklediğiniz schema’nın doğru çalışıp çalışmadığını test etmek için şu yolları kullanabilirsiniz:

  • Google Rich Results Test: search.google.com/test/rich-results adresine gidip yazı URL’nizi yapıştırın
  • Schema.org Validator: validator.schema.org adresi JSON-LD’yi doğrudan test ediyor
  • Google Search Console: “Zengin sonuçlar” raporu gerçek indeksleme hatalarını gösteriyor

Geliştirme sürecinde schema çıktısını görüntülemek için şu yardımcı fonksiyonu kullanabilirsiniz:

// Sadece WP_DEBUG modunda calisir, uretim ortaminda kullanmayin
function wpblog_debug_schema_output() {
    if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
        return;
    }

    if ( ! is_single() ) {
        return;
    }

    // wp_head hook'larinda kayitli schema fonksiyonlarini listele
    global $wp_filter;
    $hooks = array();

    if ( isset( $wp_filter['wp_head'] ) ) {
        foreach ( $wp_filter['wp_head']->callbacks as $priority => $callbacks ) {
            foreach ( $callbacks as $callback ) {
                $function_name = '';
                if ( is_string( $callback['function'] ) ) {
                    $function_name = $callback['function'];
                }
                if ( strpos( $function_name, 'schema' ) !== false ) {
                    $hooks[] = 'Priority ' . $priority . ': ' . $function_name;
                }
            }
        }
    }

    if ( ! empty( $hooks ) ) {
        echo '<!-- Schema fonksiyonlari: ' . implode( ', ', $hooks ) . ' -->';
    }
}
add_action( 'wp_head', 'wpblog_debug_schema_output', 999 );

Mevcut SEO Eklentisiyle Çakışma Önleme

Yoast SEO veya Rank Math gibi eklentiler zaten bazı schema’lar ekliyor. Çift schema eklemek Google’ı karıştırabilir. Bu yüzden fonksiyonlarınızı koşullu hale getirin:

function wpblog_schema_conflict_check() {
    // Yoast SEO aktifse article schema ekleme
    if ( defined( 'WPSEO_VERSION' ) ) {
        return false;
    }
    // Rank Math aktifse ekleme
    if ( class_exists( 'RankMath' ) ) {
        return false;
    }
    // All in One SEO aktifse ekleme
    if ( class_exists( 'AIOSEOPluginAIOSEO' ) ) {
        return false;
    }
    return true;
}

// Bu kontrolu article ve breadcrumb fonksiyonlarinin basina ekleyin:
// if ( ! wpblog_schema_conflict_check() ) { return; }

Bu kontrolü wpblog_add_article_schema() ve wpblog_add_breadcrumb_schema() fonksiyonlarının en başına ekleyin. FAQ ve Author schema için genellikle çakışma olmaz çünkü SEO eklentileri bu detaylı schema’ları otomatik oluşturmuyor.

Gerçek Dünya Senaryosu: Çok Kategorili Blog

Diyelim ki hem teknoloji hem de yemek tarifleri içeren bir blogunuz var. Teknoloji yazılarında TechArticle, tarif yazılarında Recipe schema kullanmak istiyorsunuz. Bu durumda şöyle bir yapı kurabilirsiniz:

  • Teknoloji kategorisindeki yazılar TechArticle tipiyle işaretleniyor
  • Tarif kategorisindeki yazılar tamamen farklı bir Recipe schema bloğu alıyor
  • Diğer tüm yazılar standart Article schema alıyor

wpblog_get_schema_type_by_category() fonksiyonuna tarif ve recipe slug’larını ekleyip, ana schema fonksiyonunda bu tip için ayrı bir dal açmak yeterli. Bu sayede tek bir kod tabanıyla onlarca farklı içerik tipini yönetebiliyorsunuz.

Performance açısından bakıldığında, tüm bu schema fonksiyonları wp_head‘e bağlı olduğundan sayfa yüklenme zamanına neredeyse sıfır ek yük bindiriyor. JSON-LD blokları küçük metin parçaları olduğundan, sayfa ağırlığına katkıları birkaç kilobaytı geçmiyor.

Sonuç

functions.php üzerinden JSON-LD Schema Markup eklemek, herhangi bir eklentiye bağımlı kalmadan sitenizin SEO altyapısını güçlendirmenin en temiz yolu. Bu yazıda ele aldığımız yaklaşımların özeti şöyle:

  • Article Schema ile her yazı sayfası temel yapılandırılmış veriyi otomatik alıyor
  • BreadcrumbList ile navigasyon bilgisi arama sonuçlarına yansıyor
  • FAQPage Schema ile soru-cevap bölümleri zengin sonuç formatında görünebiliyor
  • Person Schema yazar sayfalarında güven sinyalini artırıyor
  • Organization ve WebSite Schema ile sitenin genel varlığı Google’a tanıtılıyor
  • Çakışma kontrolü ile mevcut SEO eklentileriyle barış içinde çalışabiliyorsunuz

Bu kodları production ortamına almadan önce mutlaka Google Rich Results Test aracıyla doğrulayın. Schema hataları sizi zengin sonuçlardan mahrum bırakabileceği gibi, bazen ceza da gündeme gelebiliyor. Doğru yapılandırılmış schema ise hem botlara hem de kullanıcılara içeriğiniz hakkında net, güvenilir sinyaller veriyor. Bu da uzun vadede organik trafik üzerinde somut bir etki yaratıyor.

Bir yanıt yazın

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