WordPress Yazılarına functions.php ile FAQ Schema Ekleme
SEO dünyasında yapısal veri, artık “olursa iyi olur” kategorisinden çıkıp “olmazsa olmaz” kategorisine geçti. Özellikle Google’ın arama sonuçlarında soru-cevap kutucukları göstermeye başlamasıyla birlikte, FAQ Schema sitenize ciddi bir görünürlük avantajı sağlıyor. WordPress kullanıyorsanız ve her yazıya özel FAQ şeması eklemek istiyorsanız, bunu plugin olmadan, tamamen functions.php üzerinden yapabilirsiniz. Bu yazıda sıfırdan başlayıp production-ready bir FAQ Schema sistemi kuracağız.
FAQ Schema Nedir ve Neden Önemlidir?
FAQ Schema, Google’ın anlayabileceği JSON-LD formatında yazılmış yapısal veridir. Bir sayfanın soru-cevap bölümlerini makineye okunabilir hale getirir. Doğru implemente edildiğinde arama sonuçlarında şu avantajları sağlar:
- Rich Snippet görünümü: Sayfanızın altında açılıp kapanan soru-cevap blokları görünür
- Daha fazla SERP alanı: Normal 2-3 satır yerine 5-10 satırlık alan kaplayabilirsiniz
- Click-through rate artışı: Görünürlük arttıkça tıklama oranı da artar
- Voice search uyumu: Sesli aramalarda doğrudan cevap kaynağı olabilirsiniz
Gerçek dünya örneği verelim: Bir WooCommerce mağazanız var ve her ürün kategorisi için SSS sayfaları yazıyorsunuz. Her yazıya manuel JSON-LD eklemek hem zaman kaybıdır hem de hata yapma riskini artırır. Bunu functions.php ile otomatize etmek, hem işinizi kolaylaştırır hem de tutarlılık sağlar.
Temel Yaklaşım: Custom Fields ile FAQ Verisi Toplama
En sağlıklı yöntem, WordPress’in kendi custom field sistemini kullanmaktır. Her soruyu ve cevabı ayrı field’larda tutacağız, sonra bunları JSON-LD formatında sayfaya inject edeceğiz.
Önce temel yapıyı anlayalım. Her yazı için birden fazla soru-cevap çifti ekleyebilmeliyiz. Bunu yapmak için repeater mantığında bir yapı kuracağız. WordPress native custom fields bunu doğrudan desteklemez, bu yüzden soru ve cevapları sıralı şekilde saklayacağız.
Şöyle düşünün: faq_question_1, faq_answer_1, faq_question_2, faq_answer_2 şeklinde field’lar oluşturacağız. Bu old-school ama güvenilir bir yöntem.
functions.php’ye FAQ Meta Box Ekleme
İlk adımda WordPress edit ekranına güzel bir meta box ekleyelim:
// functions.php
function faq_schema_meta_box_ekle() {
add_meta_box(
'faq_schema_box',
'FAQ Schema - Soru Cevap',
'faq_schema_meta_box_render',
array('post', 'page'),
'normal',
'high'
);
}
add_action('add_meta_boxes', 'faq_schema_meta_box_ekle');
function faq_schema_meta_box_render($post) {
wp_nonce_field('faq_schema_nonce_action', 'faq_schema_nonce');
$faq_sayisi = get_post_meta($post->ID, '_faq_sayisi', true);
$faq_sayisi = $faq_sayisi ? intval($faq_sayisi) : 3;
echo '<div id="faq-container">';
for ($i = 1; $i <= $faq_sayisi; $i++) {
$soru = get_post_meta($post->ID, '_faq_soru_' . $i, true);
$cevap = get_post_meta($post->ID, '_faq_cevap_' . $i, true);
echo '<div class="faq-item" style="border:1px solid #ddd; padding:15px; margin-bottom:10px; border-radius:4px;">';
echo '<p><strong>Soru ' . $i . ':</strong></p>';
echo '<input type="text" name="faq_soru_' . $i . '" value="' . esc_attr($soru) . '" style="width:100%; margin-bottom:8px;" placeholder="Soruyu buraya yazin..." />';
echo '<p><strong>Cevap ' . $i . ':</strong></p>';
echo '<textarea name="faq_cevap_' . $i . '" rows="3" style="width:100%;" placeholder="Cevabi buraya yazin...">' . esc_textarea($cevap) . '</textarea>';
echo '</div>';
}
echo '</div>';
echo '<input type="hidden" name="faq_sayisi" value="' . esc_attr($faq_sayisi) . '" />';
echo '<button type="button" onclick="faqSayi++" style="margin-top:10px; padding:8px 15px;">+ Soru Ekle</button>';
}
Bu kod WordPress edit ekranının altına güzel bir meta box ekler. Şimdi kaydetme işlemini halledelim.
FAQ Verilerini Kaydetme
function faq_schema_kaydet($post_id) {
// Nonce kontrolu
if (!isset($_POST['faq_schema_nonce'])) {
return $post_id;
}
if (!wp_verify_nonce($_POST['faq_schema_nonce'], 'faq_schema_nonce_action')) {
return $post_id;
}
// Autosave kontrolu
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return $post_id;
}
// Yetki kontrolu
if (!current_user_can('edit_post', $post_id)) {
return $post_id;
}
$faq_sayisi = isset($_POST['faq_sayisi']) ? intval($_POST['faq_sayisi']) : 3;
update_post_meta($post_id, '_faq_sayisi', $faq_sayisi);
for ($i = 1; $i <= $faq_sayisi; $i++) {
if (isset($_POST['faq_soru_' . $i])) {
$soru = sanitize_text_field($_POST['faq_soru_' . $i]);
$cevap = sanitize_textarea_field($_POST['faq_cevap_' . $i]);
if (!empty($soru) && !empty($cevap)) {
update_post_meta($post_id, '_faq_soru_' . $i, $soru);
update_post_meta($post_id, '_faq_cevap_' . $i, $cevap);
} else {
// Bos birakilmissa temizle
delete_post_meta($post_id, '_faq_soru_' . $i);
delete_post_meta($post_id, '_faq_cevap_' . $i);
}
}
}
}
add_action('save_post', 'faq_schema_kaydet');
Güvenlik açısından dikkat ettiğimiz noktalara bakalım:
- Nonce doğrulama: CSRF saldırılarını önler
- Autosave kontrolü: Gereksiz kaydı engeller
- Sanitize kullanımı: XSS saldırılarına karşı koruma
- Yetki kontrolü: Sadece yetkili kullanıcılar düzenleyebilir
JSON-LD Schema’yı Sayfaya Enjekte Etme
Asıl kritik kısım burasıdır. Schema verisi içinde veya sayfa sonunda olabilir. Google her ikisini de kabul eder ama SEO community’sinin genel kabulü içine koymaktır:
function faq_schema_json_ld_ekle() {
// Sadece tekil yazilarda calis
if (!is_singular(array('post', 'page'))) {
return;
}
global $post;
$faq_sayisi = get_post_meta($post->ID, '_faq_sayisi', true);
if (!$faq_sayisi) {
return;
}
$faq_listesi = array();
for ($i = 1; $i <= intval($faq_sayisi); $i++) {
$soru = get_post_meta($post->ID, '_faq_soru_' . $i, true);
$cevap = get_post_meta($post->ID, '_faq_cevap_' . $i, true);
if (!empty($soru) && !empty($cevap)) {
$faq_listesi[] = array(
'@type' => 'Question',
'name' => esc_html($soru),
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => wp_strip_all_tags($cevap)
)
);
}
}
// Hic soru yoksa cikis
if (empty($faq_listesi)) {
return;
}
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => $faq_listesi
);
$json_output = wp_json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
echo '<script type="application/ld+json">' . "n";
echo $json_output;
echo "n" . '</script>' . "n";
}
add_action('wp_head', 'faq_schema_json_ld_ekle');
Burada JSON_UNESCAPED_UNICODE flag’i kritik. Türkçe karakterler olmadan düzgün çalışmaz. JSON_PRETTY_PRINT ise debug sırasında çıktıyı okunabilir kılar, production’da kaldırabilirsiniz.
Dinamik Soru Ekleme: JavaScript ile Meta Box Geliştirme
Üç sabit soru yeterli gelmeyebilir. Dinamik olarak soru ekleyip çıkaralım:
function faq_schema_js_ekle($hook) {
// Sadece post/page edit sayfasinda yukle
if (!in_array($hook, array('post.php', 'post-new.php'))) {
return;
}
?>
<script type="text/javascript">
var faqSayac = parseInt(document.querySelector('[name="faq_sayisi"]').value) || 3;
document.querySelector('button[onclick]').onclick = function() {
faqSayac++;
var container = document.getElementById('faq-container');
var yeniDiv = document.createElement('div');
yeniDiv.className = 'faq-item';
yeniDiv.style.cssText = 'border:1px solid #ddd; padding:15px; margin-bottom:10px; border-radius:4px;';
yeniDiv.innerHTML = `
<p><strong>Soru ${faqSayac}:</strong>
<span style="color:red; cursor:pointer; float:right;" onclick="this.closest('.faq-item').remove()">Kaldir</span></p>
<input type="text" name="faq_soru_${faqSayac}" style="width:100%; margin-bottom:8px;" placeholder="Soruyu buraya yazin..." />
<p><strong>Cevap ${faqSayac}:</strong></p>
<textarea name="faq_cevap_${faqSayac}" rows="3" style="width:100%;" placeholder="Cevabi buraya yazin..."></textarea>
`;
container.appendChild(yeniDiv);
document.querySelector('[name="faq_sayisi"]').value = faqSayac;
};
</script>
<?php
}
add_action('admin_footer', 'faq_schema_js_ekle');
Özel Post Type Desteği Ekleme
WooCommerce ürünleri veya özel post type’larınız varsa, meta box’u onlara da açmak gerekebilir. Örneğin bir hukuk bürosu sitesinde hizmetler post type’ı için:
function faq_desteklenen_post_types() {
return apply_filters('faq_schema_post_types', array(
'post',
'page',
'product',
'hizmetler',
'sss'
));
}
// Meta box fonksiyonunu guncelle
function faq_schema_meta_box_ekle_v2() {
$desteklenen_tipler = faq_desteklenen_post_types();
add_meta_box(
'faq_schema_box',
'FAQ Schema - Soru Cevap',
'faq_schema_meta_box_render',
$desteklenen_tipler,
'normal',
'high'
);
}
add_action('add_meta_boxes', 'faq_schema_meta_box_ekle_v2');
// Schema inject fonksiyonunu da guncelle
function faq_schema_json_ld_v2() {
$desteklenen_tipler = faq_desteklenen_post_types();
if (!is_singular($desteklenen_tipler)) {
return;
}
// ... geri kalan kod ayni
}
apply_filters kullanmamızın sebebi, başka bir yerden (plugin veya child theme) bu listeye eklemek istediğimizde kodu değiştirmeden yapabilmemizdir.
WooCommerce Ürün Sayfaları için Özel Entegrasyon
WooCommerce ürün sayfalarında FAQ schema, müşterilerin sıkça sorduğu sorulara hızlı cevap verir. Hem SEO hem UX açısından değerlidir:
function woo_urun_faq_schema() {
if (!is_product()) {
return;
}
global $post;
$urun_id = $post->ID;
// Hem urune ozel faq hem de genel urun kategorisi faq'i birlestir
$faq_listesi = array();
$faq_sayisi = get_post_meta($urun_id, '_faq_sayisi', true);
if ($faq_sayisi) {
for ($i = 1; $i <= intval($faq_sayisi); $i++) {
$soru = get_post_meta($urun_id, '_faq_soru_' . $i, true);
$cevap = get_post_meta($urun_id, '_faq_cevap_' . $i, true);
if (!empty($soru) && !empty($cevap)) {
$faq_listesi[] = array(
'@type' => 'Question',
'name' => esc_html($soru),
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => wp_strip_all_tags($cevap)
)
);
}
}
}
// Urun kategorisinden ortak sorulari da ekle
$kategoriler = wp_get_post_terms($urun_id, 'product_cat');
foreach ($kategoriler as $kategori) {
$kat_faq = get_term_meta($kategori->term_id, 'kategori_faq', true);
if (!empty($kat_faq) && is_array($kat_faq)) {
$faq_listesi = array_merge($faq_listesi, $kat_faq);
}
}
if (empty($faq_listesi)) {
return;
}
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => $faq_listesi
);
echo '<script type="application/ld+json">';
echo wp_json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
echo '</script>';
}
add_action('wp_head', 'woo_urun_faq_schema');
Schema Doğrulama ve Debug Yardımcı Fonksiyonu
Geliştirme sırasında schema’nızın doğru üretilip üretilmediğini test etmek için basit bir debug fonksiyonu:
function faq_schema_debug_goster() {
// Sadece admin ve debug modda calis
if (!current_user_can('manage_options') || !WP_DEBUG) {
return;
}
if (!isset($_GET['faq_debug'])) {
return;
}
global $post;
if (!$post) {
return;
}
$faq_sayisi = get_post_meta($post->ID, '_faq_sayisi', true);
$debug_data = array(
'post_id' => $post->ID,
'post_title' => $post->post_title,
'faq_sayisi' => $faq_sayisi,
'faqlar' => array()
);
for ($i = 1; $i <= intval($faq_sayisi); $i++) {
$soru = get_post_meta($post->ID, '_faq_soru_' . $i, true);
$cevap = get_post_meta($post->ID, '_faq_cevap_' . $i, true);
if (!empty($soru)) {
$debug_data['faqlar'][] = array(
'index' => $i,
'soru' => $soru,
'cevap' => $cevap
);
}
}
echo '<!-- FAQ SCHEMA DEBUG -->';
echo '<pre style="display:none;">';
print_r($debug_data);
echo '</pre>';
echo '<!-- /FAQ SCHEMA DEBUG -->';
}
add_action('wp_footer', 'faq_schema_debug_goster');
URL’e ?faq_debug=1 ekleyerek kaynak kodunda debug bilgisini görebilirsiniz. Bu sadece admin kullanıcılara ve WP_DEBUG açıkken çalışır.
Cache Uyumlu Schema Üretimi
Eğer sitenizde Redis veya object cache kullanıyorsanız, her sayfa yüklemesinde meta sorgusunu tekrarlamak gereksizdir:
function faq_schema_cache_ile_getir($post_id) {
$cache_key = 'faq_schema_' . $post_id;
$cached = wp_cache_get($cache_key, 'faq_schema');
if (false !== $cached) {
return $cached;
}
$faq_listesi = array();
$faq_sayisi = get_post_meta($post_id, '_faq_sayisi', true);
if (!$faq_sayisi) {
wp_cache_set($cache_key, array(), 'faq_schema', HOUR_IN_SECONDS);
return array();
}
for ($i = 1; $i <= intval($faq_sayisi); $i++) {
$soru = get_post_meta($post_id, '_faq_soru_' . $i, true);
$cevap = get_post_meta($post_id, '_faq_cevap_' . $i, true);
if (!empty($soru) && !empty($cevap)) {
$faq_listesi[] = array(
'@type' => 'Question',
'name' => esc_html($soru),
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => wp_strip_all_tags($cevap)
)
);
}
}
// 1 saatlik cache
wp_cache_set($cache_key, $faq_listesi, 'faq_schema', HOUR_IN_SECONDS);
return $faq_listesi;
}
// Post guncellendikce cache'i temizle
function faq_schema_cache_temizle($post_id) {
wp_cache_delete('faq_schema_' . $post_id, 'faq_schema');
}
add_action('save_post', 'faq_schema_cache_temizle');
Sık Yapılan Hatalar ve Çözümleri
Gerçek dünyada bu sistemi kurarken karşılaştığım problemler ve çözümleri:
Türkçe karakter sorunu: json_encode yerine her zaman wp_json_encode ve JSON_UNESCAPED_UNICODE kullanın. Aksi halde ş harfi u015f olarak encode edilir ve Google bunu okuyamazken bazı validator’lar hata verir.
Boş schema inject sorunu: Soru veya cevap alanlarından biri boşsa o soru-cevap çiftini schema’ya dahil etmeyin. Google boş text alanlarını hata olarak işaretler.
Duplicate schema sorunu: Hem tema hem de bir SEO plugin’i (Yoast, RankMath gibi) schema inject ediyorsa çakışma olabilir. Şu kontrolü ekleyin:
function faq_schema_kontrol_et() {
// Yoast aktifse ve kendi FAQ schema'si varsa bizimkini ekleme
if (defined('WPSEO_VERSION') && get_post_meta(get_the_ID(), '_yoast_wpseo_schema_page_type', true) === 'FAQPage') {
return false;
}
// RankMath aktifse kontrol et
if (class_exists('RankMath') && RankMathHelper::get_settings('general.faq_schema')) {
return false;
}
return true;
}
Meta query performansı: Çok sayıda soru için 10+ meta sorgusu olabilir. Bunu önlemek için tüm FAQ’ları tek bir serialized field’da saklayabilirsiniz. Ancak bu durumda veri düzenleme karmaşıklaşır. Orta yol olarak maksimum 7-8 soruyla sınırlamak iyi bir pratiktir.
Google Search Console’da Doğrulama
Schema’nızı ekledikten sonra şu adımları izleyin:
- Google Rich Results Test:
search.google.com/test/rich-resultsadresine sayfanızın URL’ini girin - Schema Markup Validator:
validator.schema.orgadresinde test edin - Search Console: “URL Denetimi” aracıyla sayfanızı Googlebot gözünden görün
Sayfada schema görünmesine rağmen Search Console’da “Soru-Cevap” rich snippet görünmüyorsa, Google’ın sayfayı henüz taramadığı anlamına gelir. “Dizin Oluşturmayı İste” butonuyla manuel tetikleyin.
Sonuç
Bu sistemin güzelliği tamamen WordPress native araçlarını kullanması. Plugin bağımlılığı yok, eklenti çakışması riski minimum, kontrol tamamen sizde. functions.php’ye eklediğiniz bu kodlar, her yazınız için düzenli, geçerli FAQ Schema üretir.
Başlarken küçük bir tavsiye: Önce bir test yazısına 3-4 soru ekleyin, Google Rich Results Test ile doğrulayın, schema’nın düzgün üretildiğini görün, sonra siteye yayınlayın. WooCommerce ürünleri için ayrı bir FAQ seti oluşturmayı da ihmal etmeyin, zira ürün sayfalarındaki soru-cevap rich snippet’leri conversion rate’i doğrudan etkiler.
Cache katmanını eklemeden geçmeyin. Özellikle yüksek trafikli sitelerde her sayfa yüklemesinde 5-6 extra meta sorgusu ciddi bir yük oluşturabilir. wp_cache_get ve wp_cache_set ikilisi bu sorunu zarif biçimde çözer.
Uzun vadede bu yapıyı genişletmek isterseniz, FAQ’ları kategorilere göre gruplamak veya taxonomy bazlı ortak sorular eklemek için altyapı zaten hazır. apply_filters hook’larını yerleştirdiğimiz için her şey genişletilebilir durumda.
