Programatik SEO, tek bir sayfa şablonunu elinizdeki veriyle çoğaltıp arama motorlarında bulunacak yüzlerce, binlerce sayfa üretmek demek. Bir ilan sitesinde bu, her şehir için, her şehir ve ilan türü ikilisi için, her şehir ve özellik ikilisi için ayrı bir sayfa anlamına geliyor. Şablonu yazmak işin kolay kısmı. Asıl emek şu kararlara gidiyor: hangi kombinasyonların Google'da yer almayı hak ettiği, filtrelerin sonsuz sayıda URL üretmesinin nasıl önleneceği, her sayfanın telefonda hızlı açılan eksiksiz bir HTML olarak nasıl geleceği ve kiralanan ya da satılan ilanın sayfasına ne olacağı.
Örneklerin çoğu, Ocak 2026'daki ilk commit'inden beri uçtan uca geliştirdiğimiz, Finlandiya'ya yönelik bir emlak arama platformundan. Platformun adını paylaşmıyoruz, bu yüzden yazıda ekran görüntüsü ya da URL örneği yok. İlk commit'ten dokuz ay sonra, Eylül 2026'da platform Google Arama'dan 72,8 bin tıklama ve 1,9 milyon gösterim aldı. Tıklamaların büyük çoğunluğu Finlandiya'dan ve mobil cihazlardan geldi.
Platform kısaca
Finlandiya'da ev kiralamak ya da satın almak isteyen biri, piyasada ne olduğunu görmek için birkaç ilan portalını, emlak ofislerinin sitelerini ve konut şirketlerinin sayfalarını tek tek gezmek zorunda. Platform bu ilanları tek bir aramada topluyor. Python ve Scrapy ile yazılmış crawler'lar her kaynaktan ilanları çekiyor. Supabase'teki bir kayıt tablosu hangi crawler'ın çalışacağını belirliyor ve her çalışmanın kaydını tutuyor, bir yönetim paneli de crawler'ların durumunu gösteriyor. Web sitesi, Cloudflare Workers üzerinde çalışan bir Next.js uygulaması. Sitede kiralık ve satılık ilanlarla yeni projeler, Leaflet ile hazırlanmış haritalar, fiyat geçmişi, fiyatı düşen ilan listeleri, her şehir için ayrı bir sayfa ve eşyalı, evcil hayvana uygun, saunalı, balkonlu gibi seçkiler var.
Aynı kod tabanı başka ülkelerdeki kardeş siteleri de çalıştırıyor; arayüz 18 dilde. Expo ve React Native ile geliştirdiğimiz iOS uygulaması yayında, Android sürümü yolda. Platform için bir MCP sunucusu da geliştirme aşamasında. Projenin kapsamını vaka çalışması sayfasında anlattık.
Toplayıcı siteler neden bir adım geriden başlar
Satıcıların ilanı doğrudan girdiği bir pazar yerinde ilan metni o siteye aittir. Toplayıcı (aggregator) sitede durum farklı: Gösterdiğiniz her ilan önce başka bir yerde, çoğu zaman da kelimesi kelimesine aynı metinle yayımlanmıştır. Google'ın spam politikaları bu kalıbı açıkça anıyor. Ölçeklendirilmiş içeriği kötüye kullanma başlığı altındaki örneklerden biri, feed'leri, arama sonuçlarını veya başka içerikleri kazıyarak kullanıcıya pek değer katmayan çok sayıda sayfa üretmek.
Bu yüzden her sayfa türü için şunu sorun: Bu sayfa, kaynakların vermediği neyi veriyor? Bu platformda cevap, ancak veriler bir araya gelince ortaya çıkan şeyler: bütün kaynakları kapsayan tek bir arama, ilanların harita üzerinde görünmesi, fiyat geçmişi, fiyatı düşen ilanlar ve bir şehirdeki evcil hayvana uygun kiralıklar gibi birden çok kaynaktan beslenen seçkiler.
Trafiğin hangi sayfalardan geleceği de buna bağlı. Tek bir ilan sayfası, aynı metni taşıyan orijinal ilanla yarışır; Google da birbirinin neredeyse aynısı iki sayfadan genelde yalnızca birini gösterir. Şehir ya da seçki sayfasının ise başka bir yerde orijinali yok. Bu platformda da SEO için kurgulanan sayfalar şehir sayfaları ve seçkiler. Bir toplayıcının SEO planını bu tür sayfaların etrafında kurun, ilan sayfalarını da onların altındaki uzun kuyruk olarak düşünün.
Türkiye'de benzer bir toplayıcı kurmayı düşünüyorsanız bir not: İlan sahibinin adı ve telefon numarası, 6698 sayılı KVKK kapsamında kişisel veri. Bu bilgileri başka sitelerden toplayıp yeniden yayımlamanın hukuki dayanağını, SEO'yu düşünmeye başlamadan önce netleştirin.
Hangi sayfalar Google'da olmalı
Programatik SEO'nun asıl kararı burada veriliyor ve iki soru bu kararı vermeye yetiyor. İnsanlar bu kalıpta arama yapıyor mu ("Ankara kiralık daire", "Çankaya eşyalı kiralık daire" gibi)? Sayfada bugün işe yarayacak kadar aktif ilan var mı? İlk soruyu geçemeyen sayfa, kimsenin ihtiyaç duymadığı bir URL'dir. İkinciyi geçemeyen sayfa zayıf içerik sayılır; tamamen boş olanı da Search Console büyük ihtimalle soft 404 olarak raporlar. Yayından önce hangi kalıpların arandığını bir anahtar kelime aracıyla görebilirsiniz; yayından sonra Search Console'daki sorgular size çok daha fazlasını söyler.
Hangi özelliklerin seçkiye dönüşeceği de pazara göre değişir. Finlandiya'daki sauna seçkisinin Türkiye'deki karşılığı belki "site içinde" ya da "öğrenciye uygun" olur. Bunu da arama verisine bakarak seçin.
Sayfa türü | Örnek | Dizine eklensin mi? |
|---|---|---|
İlan | Kiralık tek bir daire | Evet, ilan yayındayken |
Şehir | Tampere'de kiralık daireler | Evet |
Şehir ve ilan türü | Espoo'da yeni projeler | Evet, ilan varsa |
Şehir ve özellik (seçki) | Turku'da eşyalı kiralıklar | Evet, belirli bir ilan sayısının üstündeyse |
Serbest filtre kombinasyonları | 2 oda, 600 ile 800 euro arası, en üst kat | Hayır |
Sıralama ve harita görünümleri | Önce en ucuz; belli koordinatlara kaydırılmış harita | Hayır |
Sayfalama | Helsinki kiralıkları, 7. sayfa | Taranabilir; her sayfanın kendi URL'si ve canonical'ı olsun |
Seçkiler için bir alt sınır belirleyin: Aktif ilan sayısı bu sınırın altına düşen sayfa noindex alsın ve site haritasından çıksın, sayı toparlanınca da geri gelsin. Başlangıç için beş ilan makul bir tahmin; Search Console hangi küçük sayfaların hâlâ gösterim aldığını gösterdikçe ayarlayın. Sınır koymazsanız birkaç yüz yerleşim yeri ile bir düzine özelliğin çarpımı, her biri tek bir daire gösteren binlerce sayfa demek.
Filtreleri ve sıralamaları taramanın dışında tutun
İlan siteleri URL sayısının kontrolünü en çok filtrelerde kaybeder. Fiyat aralığı, oda sayısı, metrekare, kat, sıralama ve harita konumu birbiriyle çarpılınca ilan sayısının çok üstünde URL çıkar ve Googlebot bunları tek tek gezmeye hazırdır. Google'ın filtreli (faceted) gezinme sayfası seçenekleri sıralıyor. Filtreli URL'lerin dizine girmesi gerekmiyorsa parametreleri robots.txt ile engelleyin ya da filtre durumunu URL'de # işaretinden sonraki kısımda (fragment) tutun; Google fragment'leri genelde taramaz. Bazılarının dizine girmesini istiyorsanız parametreleri & ile ayırın, filtreleri hep aynı sırada yazın ve sonuç vermeyen kombinasyonlar için 404 döndürün.
Yönetmesi en kolay kurulum şu: Dizine girecek kombinasyonlara (şehir, ilan türü, özellik) kendi sade URL yolunu verin, geri kalan her şeyi crawler'ların uzak tutulduğu query parametrelerinde bırakın.
User-agent: *
Disallow: /*?*sort=
Disallow: /*?*price_min=
Disallow: /*?*price_max=
Disallow: /*?*bounds=Parametre adları örnek, kendi adlarınızı kullanın. En kötü durum haritalarda: Görünen alanı query string'e yazan bir harita, her kaydırma ve yakınlaştırmada yeni bir URL üretir. Bu bilgiyi fragment'te ya da taranabilir linklerin dışında tutun. robots.txt'nin yalnızca taramayı kontrol ettiğini de unutmayın: Çok sayıda link alan engelli bir URL, içeriği olmadan da olsa arama sonuçlarında görünebilir.
Her ilanın bir sonu olsun
Emlak ilanlarının ömrü kısa. Daire kiralanır, ev satılır ve geçen hafta üst sıralarda çıkan sayfa artık kimsenin alamayacağı bir evi anlatır. Google'ın tarama bütçesi sayfası kalıcı olarak kaldırılan sayfalar için net: 404 ya da 410 döndürün. Google, 429 dışındaki bütün 4xx kodlarını aynı şekilde ele alıyor; dizindeki bir sayfa bu kodlardan birini döndürmeye başlayınca dizinden çıkıyor.
Sık başvurulan alternatif, sayfayı "Bu ilan artık yayında değil" uyarısıyla açık tutmak. Sayfada geriye kalan büyük ölçüde bu uyarıysa Search Console onu büyük ihtimalle soft 404 olarak raporlar, siz de o sayfaya tarama bütçesi harcamaya devam edersiniz. Biten ilanları şehir sayfasına yönlendirmek, eski bir linkten gelen kullanıcıyı işe yarar bir yere götürür ama aradığı dairenin yerini tutmaz. Yönlendirmeyi, aynı mülkün kendi sitenizde yeni bir URL'ye taşındığı durumlara saklayın.
Toplayıcıların burada kendine özgü iki tuzağı var. Birincisi, bir taramada görünmeyen ilan hâlâ piyasada olabilir. Bir crawler gece bozulduğunda döndürmediği her ilanı bitmiş sayarsanız, bir kaynağın bütün ilanlarına 404 verir, ertesi gün de Google'dan hepsini yeniden keşfetmesini istemiş olursunuz. Bir ilanın durum kodunu değiştirmeden önce, o ilanı kaçıran crawler çalışmasının gerçekten başarılı olup olmadığına bakın. Bu platformdaki gibi her çalışmayı kaydeden bir tablo ve crawler'ların durumunu gösteren bir panel, "ilan bitti" ile "crawler bozuldu"yu ayırt etmenizi sağlar.
İkinci tuzak kimlikle ilgili. Aynı daire çoğu zaman iki üç kaynakta birden yayımlanır, bir ay sonra da yeni bir ilan numarasıyla yeniden ilana çıkar. URL'yi kaynağın ilan numarasına bağlarsanız hem kopya sayfalar yayımlarsınız hem de daire her yeniden ilana çıktığında bir sayfayı çöpe atarsınız. URL'yi adres, metrekare ve kat üzerinden eşleştirdiğiniz kendi mülk kaydınıza bağlamak ikisini de çözer. Bu eşleştirmeyi doğru yapmak da büyük ölçüde bir veri kalitesi işi.
Her sayfa tam HTML olarak gelsin ve telefonda hızlı açılsın
Google her sitenin mobil sürümünü dizine ekliyor, bu platformda da tıklamaların çoğu telefondan geliyor. Yani önemli olan mobil sayfa. Google JavaScript'i çalıştırabiliyor, ama bunu ne zaman yapacağı belli olmayan ikinci bir aşamada yapıyor; üstelik her bot JavaScript çalıştırmıyor. İlan başlığı, fiyat, adres, açıklama ve ilgili ilanlara giden linkler sunucunun gönderdiği HTML'de olmalı, linkler de sıradan <a href> öğeleri olmalı.
Bu platformda Next.js, Cloudflare Workers üzerinde çalışıyor, yani sunucu tarafındaki render Cloudflare'in ağında yapılıyor. Leaflet haritaları da bunun üstüne istemci tarafında ekleniyor. Buradan çıkarılacak kural şu: Arama için önemli olan her şey sunucunun gönderdiği HTML'de olsun, harita ve filtre gibi etkileşimli parçalar onun üstüne otursun. Hız da aynı mobil kullanıcılar için önemli. İlan sayfalarını genelde optimize edilmemiş fotoğraflar ve içerikten önce yüklenen ağır harita script'leri yavaşlatır; bunun nasıl ölçüleceğini performans mühendisliği yazımızda anlattık.
Neyin değiştiğini söyleyen site haritaları
Google'ın site haritası kuralları kısa. Bir dosyada en fazla 50.000 URL olabilir ve dosya sıkıştırılmamış hâlde 50 MB'ı geçemez. Google lastmod değerini yalnızca tutarlı ve doğrulanabilir biçimde doğruysa kullanıyor, priority ve changefreq değerlerini ise tamamen yok sayıyor.
İlan sitesinde iki alışkanlık site haritalarını işe yarar hâle getirir. Birincisi, haritaları sayfa türüne göre bölmek: şehirler, seçkiler ve parçalara ayrılmış ilanlar. Search Console'daki Sayfa dizine ekleme raporu site haritasına göre filtrelenebildiği için her sayfa türünün ne kadarının dizinde olduğunu böylece ayrı ayrı görürsünüz. İkincisi, lastmod değerini okuyucunun fark edeceği son değişikliğe (fiyat, açıklama, durum) göre vermek. Her crawler çalışmasında güncellenen bir lastmod hiçbir şey ifade etmez.
Next.js App Router'da generateSitemaps, tek bir site haritası route'unu numaralı dosyalara böler. İlanlar için bir taslak (Next.js 16'da id, string'e çözülen bir promise olarak geliyor):
// app/listings/sitemap.ts
import type { MetadataRoute } from 'next'
import { countLiveListings, getLiveListings } from '@/lib/listings'
const PER_FILE = 45_000 // Google'ın 50.000 URL sınırının altında pay bırakır
const BASE_URL = 'https://example.com'
// Bu satır olmazsa dosyalar build sırasında bir kez üretilir ve bir daha değişmez.
// (cacheComponents açıksa bunun yerine 'use cache' ve cacheLife kullanın.)
export const revalidate = 3600
export async function generateSitemaps() {
const total = await countLiveListings()
return Array.from({ length: Math.ceil(total / PER_FILE) }, (_, id) => ({ id }))
}
export default async function sitemap(props: {
id: Promise<string>
}): Promise<MetadataRoute.Sitemap> {
const page = Number(await props.id)
// API'niz istek başına satır sayısını sınırlıyorsa getLiveListings parça parça çekmeli
// (Supabase varsayılan olarak en fazla 1.000 satır döndürür ve hata vermez)
const listings = await getLiveListings({ offset: page * PER_FILE, limit: PER_FILE })
return listings.map((listing) => ({
url: `${BASE_URL}/listing/${listing.slug}`,
lastModified: listing.contentChangedAt, // fiyat, metin ya da durum değişikliği; tarama zamanı değil
}))
}Dosyalar /listings/sitemap/0.xml, /listings/sitemap/1.xml diye devam eden adreslerde yayımlanır. Bunları Search Console'a tek tek gönderin ya da bir site haritası dizin dosyasında (sitemap index) toplayıp onu gönderin. Haritalarda yalnızca yayındaki ve dizine eklenebilir URL'ler olsun; biten ilanlar ve noindex almış seçkiler dışarıda kalsın.
Taslaktaki iki yorum, canlı ortamda karşınıza çıkacak iki sorun yüzünden orada. Next.js site haritası route'larını varsayılan olarak önbelleğe alır. revalidate olmadan dosyalar next build sırasında üretilir ve bir sonraki deploy'a kadar geçen haftanın ilanlarını listelemeye devam eder (build çıktısı bu dosyaları önceden üretilmiş olarak gösterir). Bu satırla Next.js her dosyayı en fazla saatte bir, arka planda yeniden üretir. Cloudflare Workers'a OpenNext ile deploy ediyorsanız bu yenilemenin çalışması için R2 ya da KV üzerinde bir incremental cache ve bir kuyruk (queue) da tanımlamanız gerekir. İkinci sorun, sonucun sessizce kesilmesi: Supabase sorguları varsayılan olarak en fazla 1.000 satır döndürür. 45.000 satırı tek istekte isteyen bir yardımcı fonksiyon 1.000 satır alır, hata da görmez; site haritanız da ilanlarınızın küçük bir kısmını kapsar ve bunu kimse fark etmez.
Yapılandırılmış veri: beklediğinizden az
Google'da kiralık ya da satılık konut ilanları için bir zengin sonuç türü yok. En yakın tür olan tatil kiralama (vacation rental), Google'da bir teknik hesap yöneticisiyle çalışan ve Hotel Center erişimi olan siteler için. İlan sitelerinin sıkça işaretlemesini eklediği site bağlantıları arama kutusu (sitelinks search box) Kasım 2024'te kaldırıldı. Eklemeye değer iki şey kalıyor: Google'ın site adını göstermek için hâlâ kullandığı WebSite işaretlemesi ve BreadcrumbList. İkincisi masaüstü sonuçlarda görünüyor; mobil sonuçlarda Ocak 2025'ten beri yalnızca alan adı gösteriliyor.
Daha fazla ülke, daha fazla dil
18 dilde arayüz ve başka ülkelerdeki kardeş siteler iki ayrı soru doğuruyor.
Önce diller. Bir sayfanın her dil sürümü hreflang ile hem kendini hem de bütün alternatiflerini listelemeli; karşılıklı olmayan işaretlemeleri Google yok sayar. Asıl ince nokta Google'ın yerelleştirilmiş sürümler sayfasında: "Bir sayfanın yerelleştirilmiş sürümleri, yalnızca sayfanın ana içeriği çevrilmeden kalırsa kopya olarak kabul edilir." İlan sayfasının ana içeriği ilan metnidir ve bu metin genelde kaynağın yayımladığı dildedir. Yalnızca menüleri çevirmek, Google'a birbirinin neredeyse aynısı 18 sayfa vermek demek. Alternatifleri yine de belirtin ki Google her kullanıcıya doğru sürümü gösterebilsin; hangi dil sürümlerinin dizine eklenebilir bir URL'ye sahip olacağına da bilerek karar verin. İlanların etrafındaki metni sizin yazdığınız ve düzgünce çevirebildiğiniz şehir ve seçki sayfaları için bunu savunmak, ilan sayfalarına göre çok daha kolay.
Ülkelere gelince, kardeş siteleri tek kod tabanından çalıştırmakta bir sakınca yok; farklı olması gereken şey ilan envanteri. Aynı spam politikası "içeriğin ölçeklendirilmiş olduğunu gizlemek için birden fazla site oluşturma"yı da örnekler arasında sayıyor. Bir ülke sitesinin çevrilmiş kopyasını başka bir alan adında yayımlamak, tam olarak bu maddenin tarif ettiği şey.
Yayındaki bir ilan sitesini denetlemek
Site zaten yayındaysa şu beş kontrol, yukarıdaki sorunların çoğunu ortaya çıkarır:
- Bir ilan sayfasının ham HTML'ini açın (kaynağı görüntüleyin ya da
curlile çekin). Başlık, fiyat, açıklama ve ilgili ilan linkleri orada mı, yoksa yalnızca boş bir yükleme iskeleti mi var? - Bittiğini bildiğiniz bir ilanın URL'sini açın. Kısa bir uyarıyla 200 dönen sayfa, soft 404 olmaya adaydır.
- Search Console'da Ayarlar'dan Tarama istatistikleri raporunu açıp Googlebot'un istediği örnek URL'lere bakın. Çoğu filtre ya da sıralama parametresi taşıyorsa tarama yanlış sayfalara gidiyor demektir.
- Sayfa dizine ekleme raporunu ilan site haritanıza göre filtreleyin ve dizindeki sayıyı yayındaki ilan sayısıyla karşılaştırın. "Bulundu: Şu anda dizine eklenmiş değil" durumunda çok sayfa varsa sorun tarama kapasitesinde ya da URL şişkinliğindedir. "Tarandı: Şu anda dizine eklenmiş değil" ya da "Kopya, Google kullanıcıdan farklı bir standart sayfa seçti" durumları kalabalıksa, Google sayfalarınızı birbirinden yeterince farklı bulmuyor demektir.
- Bir iki ilanı olan bir kasaba seçip şehir ve seçki sayfalarına bakın. Dizine eklenebilir durumdalar ve neredeyse boşlarsa yukarıdaki alt sınırı koyun.
Google, tarama bütçesi sayfasını kabaca bir milyondan fazla sayfası haftada bir değişen ya da 10.000'den fazla sayfası her gün değişen siteler için yazdığını söylüyor. Bir ilan sitesi ikinci gruba çabuk girer; oraya vardığınızda sayfanın tamamını okumaya değer.
Bir pazar yeri ya da ilan sitesi planlıyorsanız ve URL planının geliştirmeye başlamadan önce gözden geçirilmesini istiyorsanız, bu web geliştirme projelerimizde üstlendiğimiz türden bir iş.
Sık sorulan sorular
Programatik SEO Google kurallarına aykırı mı?
Hayır. Google'ın spam politikası, nasıl üretilmiş olursa olsun, asıl amacı sıralamayı manipüle etmek olan ve kullanıcıya pek bir şey katmayan sayfaları hedefliyor. Aktif ilanları, fiyatları ve haritası olan bir şehir sayfası işe yarar. Her kasaba için açılmış, "ilan bulunamadı" yazan bir sayfa yaramaz.
Filtre sayfalarını noindex ile mi, robots.txt ile mi dışarıda tutmalı?
İkisi farklı işler yapar ve birlikte çalışmaz. Google noindex etiketini ancak sayfayı tarayabilirse görür; robots.txt ile engellenmiş bir URL, noindex etiketi hiç okunmadan dizinde kalabilir. Filtreli URL'ler zaten dizindeyse önce noindex ile taranmalarına izin verin, dizinden düştükten sonra parametreleri robots.txt ile engelleyip taramayı durdurun.
Binlerce şehir sayfasının metnini yapay zeka yazabilir mi?
Yazabilir, ama ölçeklendirilmiş içerik politikası tam da bunu örnek veriyor: üretken yapay zeka araçlarıyla kullanıcıya değer katmayan çok sayıda sayfa üretmek. Şehir adını ve ilan sayısını farklı cümlelerle tekrar eden bir giriş metni hiçbir şey katmaz. Kendi verinizden çıkan metin ise katar: ortalama kira, bu hafta eklenen ilan sayısı ya da son üç ayda fiyatların nasıl değiştiği gibi. Üstelik ilanlarla aynı veritabanından geldiği için doğru da kalır.
İlgili Makaleler
MLOps Nedir? ML Modelini Canlıya Almanın Pratik Yolu
MLOps, bir modeli canlıya taşıyan ve orada işe yarar tutan pratiklerin toplamı. Hangi sırayla kurulacağını güncel MLflow ve DVC örnekleriyle anlattık, LLM tarafını da ekledik.
Devamını Oku: MLOps Nedir? ML Modelini Canlıya Almanın Pratik YoluBilgisayarlı Görü Uygulamaları: Nerede İşe Yarar, Nerede Zorlanır?
Fabrikadaki kalite kontrolden faturaya, tomografiden plaka tanımaya: bilgisayarlı görü nerede iş görüyor, nerede zorlanıyor, hazır modeller neyi değiştirdi?
Devamını Oku: Bilgisayarlı Görü Uygulamaları: Nerede İşe Yarar, Nerede Zorlanır?AI Agent Geliştirme: Ne Zaman Gerekir, Nasıl Kurulur?
AI agent, araçlarla döngü içinde çalışan bir dil modeli. Ne zaman gerektiğini, Python'da çalışan bir döngüyü, framework seçimini, güvenliği, KVKK'yı ve maliyeti anlattık.
Devamını Oku: AI Agent Geliştirme: Ne Zaman Gerekir, Nasıl Kurulur?
