WordPress Sitemap Oluşturma: functions.php ile Özel Sitemap

WordPress sitenizin arama motorları tarafından doğru şekilde taranması için sitemap dosyaları kritik öneme sahip. Çoğu geliştirici hemen bir eklenti kuruyor, ama aslında functions.php dosyasına birkaç satır kod ekleyerek hem daha hafif hem de tamamen kontrol edebildiğiniz bir sitemap sistemi kurabilirsiniz. Bu yazıda, Yoast veya RankMath gibi eklentilere bağımlı kalmadan, sıfırdan özel bir sitemap sistemi kurmayı anlatacağım.

Neden Özel Sitemap?

Eklenti kullanmak her zaman en kolay yol gibi görünür. Ama şu senaryoyu düşünün: Onlarca eklenti yüklü bir site, her biri arka planda bir şeyler yapıyor, ve siz sadece sitemap için 2 MB’lık bir eklenti paketi taşıyorsunuz. Özellikle hafif temalarda veya headless WordPress kurulumlarında bu gereksiz bir yük.

Özel sitemap sisteminin avantajları:

  • Tam kontrol: Hangi içeriklerin dahil edileceğine siz karar verirsiniz
  • Performans: Eklenti overhead’i yok, sadece ihtiyacınız olan kod
  • Özelleştirme: WooCommerce ürünleri, özel post type’lar, özel kurallar ekleyebilirsiniz
  • Hata ayıklama: Sorun çıktığında nerede bakacağınızı bilirsiniz

Temel Sitemap Yapısı

Sitemap dosyaları XML formatındadır ve Google’ın belirttiği standart bir yapı izler. En basit haliyle şöyle görünür:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://example.com/</loc>
    <lastmod>2024-01-15</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
</urlset>

Bu yapıyı dinamik olarak oluşturmak için WordPress’in rewrite kurallarını ve query sistemini kullanacağız.

functions.php’ye İlk Kod: Rewrite Kuralı

İlk adım, WordPress’e /sitemap.xml adresini tanıtmak. Bunu rewrite kuralı ekleyerek yapıyoruz:

// functions.php dosyasina ekleyin
function custom_sitemap_rewrite_rules() {
    add_rewrite_rule(
        'sitemap.xml$',
        'index.php?custom_sitemap=1',
        'top'
    );
    
    add_rewrite_rule(
        'sitemap-([a-z]+).xml$',
        'index.php?custom_sitemap=$matches[1]',
        'top'
    );
}
add_action( 'init', 'custom_sitemap_rewrite_rules' );

function custom_sitemap_query_vars( $vars ) {
    $vars[] = 'custom_sitemap';
    return $vars;
}
add_filter( 'query_vars', 'custom_sitemap_query_vars' );

Bu kodu ekledikten sonra WordPress yönetici panelinden Ayarlar > Kalıcı Bağlantılar sayfasına girip kaydet butonuna tıklamanız gerekiyor. Bu işlem rewrite kurallarını yeniler.

Ana Sitemap Index Dosyası

Büyük siteler için tek bir sitemap dosyası yeterli olmayabilir. Google, tek bir sitemap’te maksimum 50.000 URL kabul eder. Bu yüzden sitemap index sistemi kullanıyoruz:

function custom_sitemap_output() {
    $sitemap_type = get_query_var( 'custom_sitemap' );
    
    if ( empty( $sitemap_type ) ) {
        return;
    }
    
    // HTTP headers
    header( 'Content-Type: application/xml; charset=UTF-8' );
    header( 'X-Robots-Tag: noindex, follow' );
    
    if ( $sitemap_type === '1' || $sitemap_type === 'index' ) {
        custom_sitemap_index();
    } elseif ( $sitemap_type === 'posts' ) {
        custom_sitemap_posts();
    } elseif ( $sitemap_type === 'pages' ) {
        custom_sitemap_pages();
    } elseif ( $sitemap_type === 'products' ) {
        custom_sitemap_products();
    } elseif ( $sitemap_type === 'categories' ) {
        custom_sitemap_taxonomies( 'category' );
    }
    
    exit;
}
add_action( 'template_redirect', 'custom_sitemap_output' );

function custom_sitemap_index() {
    $site_url = get_bloginfo( 'url' );
    
    echo '<?xml version="1.0" encoding="UTF-8"?>' . "n";
    echo '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "n";
    
    $sitemaps = array(
        'posts'      => array( 'lastmod' => custom_sitemap_get_last_modified( 'post' ) ),
        'pages'      => array( 'lastmod' => custom_sitemap_get_last_modified( 'page' ) ),
        'categories' => array( 'lastmod' => date( 'Y-m-d' ) ),
    );
    
    // WooCommerce aktifse urunleri ekle
    if ( class_exists( 'WooCommerce' ) ) {
        $sitemaps['products'] = array( 'lastmod' => custom_sitemap_get_last_modified( 'product' ) );
    }
    
    foreach ( $sitemaps as $type => $data ) {
        echo "t<sitemap>n";
        echo "tt<loc>" . esc_url( $site_url . '/sitemap-' . $type . '.xml' ) . "</loc>n";
        echo "tt<lastmod>" . esc_html( $data['lastmod'] ) . "</lastmod>n";
        echo "t</sitemap>n";
    }
    
    echo '</sitemapindex>';
}

Blog Yazıları Sitemap’i

Asıl içeriklerin listelendiği kısım burası. Yayınlanmış tüm yazıları sorguluyor ve XML formatında çıktı veriyoruz:

function custom_sitemap_posts() {
    $site_url = get_bloginfo( 'url' );
    
    $args = array(
        'post_type'      => 'post',
        'post_status'    => 'publish',
        'posts_per_page' => -1,
        'orderby'        => 'modified',
        'order'          => 'DESC',
        'meta_query'     => array(
            array(
                'key'     => '_sitemap_exclude',
                'compare' => 'NOT EXISTS',
            ),
        ),
    );
    
    $posts = get_posts( $args );
    
    echo '<?xml version="1.0" encoding="UTF-8"?>' . "n";
    echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
             xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">' . "n";
    
    foreach ( $posts as $post ) {
        $modified = get_post_modified_time( 'Y-m-d', false, $post->ID );
        $priority = '0.7';
        
        // Yas hesaplama - yeni yazilar daha yuksek oncelik
        $post_age_days = ( time() - get_post_time( 'U', false, $post->ID ) ) / DAY_IN_SECONDS;
        if ( $post_age_days < 30 ) {
            $priority = '0.9';
        } elseif ( $post_age_days < 180 ) {
            $priority = '0.8';
        }
        
        echo "t<url>n";
        echo "tt<loc>" . esc_url( get_permalink( $post->ID ) ) . "</loc>n";
        echo "tt<lastmod>" . esc_html( $modified ) . "</lastmod>n";
        echo "tt<changefreq>weekly</changefreq>n";
        echo "tt<priority>" . $priority . "</priority>n";
        
        // Onsekli gorsel varsa ekle
        $thumbnail_id = get_post_thumbnail_id( $post->ID );
        if ( $thumbnail_id ) {
            $img_url = wp_get_attachment_image_url( $thumbnail_id, 'full' );
            $img_title = get_the_title( $post->ID );
            echo "tt<image:image>n";
            echo "ttt<image:loc>" . esc_url( $img_url ) . "</image:loc>n";
            echo "ttt<image:title>" . esc_html( $img_title ) . "</image:title>n";
            echo "tt</image:image>n";
        }
        
        echo "t</url>n";
    }
    
    echo '</urlset>';
}

Bu fonksiyonda dikkat edilmesi gereken birkaç nokta var. _sitemap_exclude meta key’i, belirli yazıları sitemap’ten çıkarmak için kullanabileceğiniz bir mekanizma. Ayrıca görsel sitemap namespace’i ekleyerek Google’ın görselleri de indexlemesini sağlıyoruz.

Sayfa ve Kategori Sitemap’leri

function custom_sitemap_pages() {
    $site_url = get_bloginfo( 'url' );
    
    $exclude_pages = array(
        get_option( 'page_for_posts' ),
        get_option( 'page_on_front' ),
    );
    
    // Ozel sayfalari da hariç tutabilirsiniz
    $exclude_meta = get_pages( array(
        'meta_key'   => '_sitemap_exclude',
        'meta_value' => '1',
    ) );
    
    foreach ( $exclude_meta as $exc ) {
        $exclude_pages[] = $exc->ID;
    }
    
    $args = array(
        'post_type'      => 'page',
        'post_status'    => 'publish',
        'posts_per_page' => -1,
        'exclude'        => array_filter( $exclude_pages ),
        'orderby'        => 'modified',
        'order'          => 'DESC',
    );
    
    $pages = get_posts( $args );
    
    echo '<?xml version="1.0" encoding="UTF-8"?>' . "n";
    echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "n";
    
    // Ana sayfa her zaman en uste
    echo "t<url>n";
    echo "tt<loc>" . esc_url( $site_url ) . "/</loc>n";
    echo "tt<changefreq>daily</changefreq>n";
    echo "tt<priority>1.0</priority>n";
    echo "t</url>n";
    
    foreach ( $pages as $page ) {
        $depth = count( get_post_ancestors( $page->ID ) );
        $priority = round( 0.9 - ( $depth * 0.1 ), 1 );
        $priority = max( 0.5, $priority );
        
        echo "t<url>n";
        echo "tt<loc>" . esc_url( get_permalink( $page->ID ) ) . "</loc>n";
        echo "tt<lastmod>" . esc_html( get_post_modified_time( 'Y-m-d', false, $page->ID ) ) . "</lastmod>n";
        echo "tt<changefreq>monthly</changefreq>n";
        echo "tt<priority>" . $priority . "</priority>n";
        echo "t</url>n";
    }
    
    echo '</urlset>';
}

function custom_sitemap_taxonomies( $taxonomy = 'category' ) {
    $terms = get_terms( array(
        'taxonomy'   => $taxonomy,
        'hide_empty' => true,
        'orderby'    => 'count',
        'order'      => 'DESC',
    ) );
    
    if ( is_wp_error( $terms ) ) {
        return;
    }
    
    echo '<?xml version="1.0" encoding="UTF-8"?>' . "n";
    echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "n";
    
    foreach ( $terms as $term ) {
        $count    = $term->count;
        $priority = '0.5';
        
        if ( $count > 50 ) {
            $priority = '0.8';
        } elseif ( $count > 20 ) {
            $priority = '0.7';
        } elseif ( $count > 5 ) {
            $priority = '0.6';
        }
        
        echo "t<url>n";
        echo "tt<loc>" . esc_url( get_term_link( $term ) ) . "</loc>n";
        echo "tt<changefreq>weekly</changefreq>n";
        echo "tt<priority>" . $priority . "</priority>n";
        echo "t</url>n";
    }
    
    echo '</urlset>';
}

Burada sayfa derinliğine göre otomatik öncelik hesaplıyoruz. Ana sayfanın altındaki sayfalar 0.9, bir alt sayfalar 0.8 gibi iniyor. Bu, arama motorlarına site yapınız hakkında anlamlı bir sinyal veriyor.

Yardımcı Fonksiyon: Son Değişiklik Tarihi

function custom_sitemap_get_last_modified( $post_type = 'post' ) {
    global $wpdb;
    
    $last_modified = $wpdb->get_var(
        $wpdb->prepare(
            "SELECT MAX(post_modified) 
             FROM {$wpdb->posts} 
             WHERE post_type = %s 
             AND post_status = 'publish'",
            $post_type
        )
    );
    
    if ( $last_modified ) {
        return date( 'Y-m-d', strtotime( $last_modified ) );
    }
    
    return date( 'Y-m-d' );
}

WooCommerce Ürün Sitemap’i

E-ticaret sitesi yönetiyorsanız ürün sayfaları sitemap’i ayrı tutmak mantıklı. Stok durumu ve fiyat bilgisini de ekleyebilirsiniz:

function custom_sitemap_products() {
    if ( ! class_exists( 'WooCommerce' ) ) {
        return;
    }
    
    $args = array(
        'post_type'      => 'product',
        'post_status'    => 'publish',
        'posts_per_page' => -1,
        'orderby'        => 'modified',
        'order'          => 'DESC',
        'tax_query'      => array(
            array(
                'taxonomy' => 'product_visibility',
                'field'    => 'name',
                'terms'    => array( 'exclude-from-catalog' ),
                'operator' => 'NOT IN',
            ),
        ),
        'meta_query'     => array(
            array(
                'key'     => '_stock_status',
                'value'   => 'outofstock',
                'compare' => '!=',
            ),
        ),
    );
    
    $products = get_posts( $args );
    
    echo '<?xml version="1.0" encoding="UTF-8"?>' . "n";
    echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
             xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">' . "n";
    
    foreach ( $products as $product ) {
        $wc_product  = wc_get_product( $product->ID );
        $modified    = get_post_modified_time( 'Y-m-d', false, $product->ID );
        
        // Stok durumuna gore daha sik guncelleme
        $changefreq = 'monthly';
        if ( $wc_product && $wc_product->is_on_sale() ) {
            $changefreq = 'daily';
        }
        
        echo "t<url>n";
        echo "tt<loc>" . esc_url( get_permalink( $product->ID ) ) . "</loc>n";
        echo "tt<lastmod>" . esc_html( $modified ) . "</lastmod>n";
        echo "tt<changefreq>" . $changefreq . "</changefreq>n";
        echo "tt<priority>0.8</priority>n";
        
        // Urun gorseli
        $thumbnail_id = get_post_thumbnail_id( $product->ID );
        if ( $thumbnail_id ) {
            $img_url   = wp_get_attachment_image_url( $thumbnail_id, 'full' );
            $img_title = get_the_title( $product->ID );
            
            echo "tt<image:image>n";
            echo "ttt<image:loc>" . esc_url( $img_url ) . "</image:loc>n";
            echo "ttt<image:title>" . esc_html( $img_title ) . "</image:title>n";
            echo "tt</image:image>n";
        }
        
        echo "t</url>n";
    }
    
    echo '</urlset>';
}

Bu fonksiyon stokta olmayan ürünleri ve katalogdan gizlenen ürünleri otomatik olarak dışarıda bırakıyor. Kampanyalı ürünler için changefreq değerini daily yaparak Google’a bu sayfaları daha sık taramasını söylüyoruz.

robots.txt ile Sitemap’i Tanıtmak

Sitemap dosyanızı oluşturduktan sonra arama motorlarına bildirmek gerekiyor. WordPress’te robots.txt’yi dinamik olarak düzenleyebilirsiniz:

function custom_sitemap_robots( $output ) {
    $site_url = get_bloginfo( 'url' );
    $output  .= "nSitemap: " . $site_url . "/sitemap.xmln";
    return $output;
}
add_filter( 'robots_txt', 'custom_sitemap_robots' );

Sitemap’i Belleğe Alma (Cache)

Her istek geldiğinde veritabanı sorgusu çalıştırmak, özellikle büyük sitelerde performans sorununa yol açabilir. Basit bir transient cache sistemi kuralım:

function custom_sitemap_cached_output() {
    $sitemap_type = get_query_var( 'custom_sitemap' );
    
    if ( empty( $sitemap_type ) ) {
        return;
    }
    
    $cache_key = 'custom_sitemap_' . $sitemap_type;
    $cached    = get_transient( $cache_key );
    
    header( 'Content-Type: application/xml; charset=UTF-8' );
    
    if ( $cached !== false ) {
        echo $cached;
        exit;
    }
    
    // Output buffering ile icerik yaka
    ob_start();
    
    switch ( $sitemap_type ) {
        case '1':
        case 'index':
            custom_sitemap_index();
            break;
        case 'posts':
            custom_sitemap_posts();
            break;
        case 'pages':
            custom_sitemap_pages();
            break;
        case 'products':
            custom_sitemap_products();
            break;
        case 'categories':
            custom_sitemap_taxonomies( 'category' );
            break;
    }
    
    $output = ob_get_clean();
    
    // 6 saat cache
    set_transient( $cache_key, $output, 6 * HOUR_IN_SECONDS );
    
    echo $output;
    exit;
}

// Cache temizleme: icerik guncellendiginde
function custom_sitemap_clear_cache( $post_id ) {
    if ( wp_is_post_revision( $post_id ) ) {
        return;
    }
    
    $post_type = get_post_type( $post_id );
    
    delete_transient( 'custom_sitemap_index' );
    delete_transient( 'custom_sitemap_1' );
    
    switch ( $post_type ) {
        case 'post':
            delete_transient( 'custom_sitemap_posts' );
            break;
        case 'page':
            delete_transient( 'custom_sitemap_pages' );
            break;
        case 'product':
            delete_transient( 'custom_sitemap_products' );
            break;
    }
}
add_action( 'save_post', 'custom_sitemap_clear_cache' );
add_action( 'delete_post', 'custom_sitemap_clear_cache' );

Sitemap’i Test Etmek

Kodları ekledikten sonra test etmeniz gereken birkaç şey var:

  • Rewrite flush: Yönetici > Ayarlar > Kalıcı Bağlantılar’a girip kaydet butonuna tıklayın
  • XML doğrulama: https://siteniz.com/sitemap.xml adresine gidin, XML yapısının doğru görüntülendiğini kontrol edin
  • Google Search Console: Sitemap’i Search Console üzerinden gönderin
  • Boyut kontrolü: 50.000 URL limitini aşıp aşmadığınızı kontrol edin

Sitemap’te sorun yaşarsanız WordPress debug modunu açın:

// wp-config.php
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

Hata loglarını /wp-content/debug.log dosyasında bulabilirsiniz.

Gerçek Dünya Senaryosu: Çok Dilli Site

WPML veya Polylang kullanan bir siteniz varsa dil bazlı sitemap eklemek gerekiyor:

function custom_sitemap_multilang_index() {
    $site_url  = get_bloginfo( 'url' );
    $languages = array( 'tr', 'en', 'de' ); // Dil kodlariniz
    
    echo '<?xml version="1.0" encoding="UTF-8"?>' . "n";
    echo '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "n";
    
    foreach ( $languages as $lang ) {
        echo "t<sitemap>n";
        echo "tt<loc>" . esc_url( $site_url . '/' . $lang . '/sitemap-posts.xml' ) . "</loc>n";
        echo "t</sitemap>n";
    }
    
    echo '</sitemapindex>';
}

Belirli İçerikleri Hariç Tutmak

Yazı veya sayfa başına sitemap dışında bırakma özelliği eklemek için meta box kullanabilirsiniz:

function custom_sitemap_meta_box() {
    $screens = array( 'post', 'page', 'product' );
    foreach ( $screens as $screen ) {
        add_meta_box(
            'sitemap_exclude',
            'Sitemap Ayarlari',
            'custom_sitemap_meta_box_html',
            $screen,
            'side',
            'low'
        );
    }
}
add_action( 'add_meta_boxes', 'custom_sitemap_meta_box' );

function custom_sitemap_meta_box_html( $post ) {
    $value = get_post_meta( $post->ID, '_sitemap_exclude', true );
    wp_nonce_field( 'sitemap_meta_box', 'sitemap_meta_box_nonce' );
    ?>
    <label>
        <input type="checkbox" name="sitemap_exclude" value="1" <?php checked( $value, '1' ); ?>>
        Bu sayfayi sitemapten cikar
    </label>
    <?php
}

function custom_sitemap_meta_box_save( $post_id ) {
    if ( ! isset( $_POST['sitemap_meta_box_nonce'] ) ) {
        return;
    }
    
    if ( ! wp_verify_nonce( $_POST['sitemap_meta_box_nonce'], 'sitemap_meta_box' ) ) {
        return;
    }
    
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    
    if ( isset( $_POST['sitemap_exclude'] ) ) {
        update_post_meta( $post_id, '_sitemap_exclude', '1' );
    } else {
        delete_post_meta( $post_id, '_sitemap_exclude' );
    }
}
add_action( 'save_post', 'custom_sitemap_meta_box_save' );

Bu meta box sayesinde editörler, herhangi bir yazıyı WordPress panelinden sitemap dışında bırakabilir. Gizli landing page’ler veya test sayfaları için çok işe yarıyor.

Sonuç

functions.php ile özel sitemap sistemi kurmak ilk bakışta karmaşık görünebilir, ama bir kez kurduğunuzda size tam anlamıyla özgürlük sağlıyor. Hangi içeriklerin dahil olacağını, öncelik değerlerinin nasıl hesaplanacağını, cache süresini, hatta WooCommerce’e özel mantıkları siz belirliyorsunuz.

Özellikle şu durumlarda özel sitemap sistemi kesinlikle değer:

  • Yüksek trafikli siteler: Eklenti overhead’i olmadan daha hızlı yanıt
  • Özel post type’lar: Eklentilerin desteklemediği içerik türleri
  • E-ticaret: Stok ve kampanya durumuna göre dinamik önceliklendirme
  • Kurumsal siteler: IT politikaları nedeniyle eklenti sayısı kısıtlı ortamlar

Kod bloklarını doğrudan functions.php‘ye ekleyebilir ya da includes/sitemap.php gibi ayrı bir dosyaya koyup require_once ile dahil edebilirsiniz. İkinci yaklaşım, kodunuzu daha düzenli tutar ve tema güncellemelerinde yönetimi kolaylaştırır.

Google Search Console’dan sitenizi manuel olarak ping etmeyi de unutmayın: https://www.google.com/ping?sitemap=https://siteniz.com/sitemap.xml adresine istek göndererek Google’ın yeni sitemap’inizi hemen taramasını sağlayabilirsiniz.

Bir yanıt yazın

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