
Yapısal Veri Hatası: Aynı Sayfada İki Şema Olursa Ne Olur?
Üç ürün sayfamızda aynı türden iki ayrı şema basılıyordu. İkisi de geçerliydi, ikisi de doğrulamadan geçiyordu, ama farklı bilgi söylüyorlardı.
Aynı sayfada aynı türden iki şema bulunduğunda Google hangisinin doğru olduğunu bilemez ve genelde ikisini birden yok sayar. Sonuç hata mesajı değil, sessizce kaybolan zengin sonuçlardır — bu yüzden aylarca fark edilmez.
Yapısal veri (schema markup) eklemek iyi bir fikirdir. Fazladan eklemek değildir.
Kendi ürün sayfalarımızı denetlerken şunu bulduk: üç sayfada aynı türden iki ayrı şema basılıyordu. İkisi de geçerliydi, ikisi de doğrulama araçlarından geçiyordu, ama birbirinden farklı bilgi söylüyorlardı.
Nasıl oluşuyor
Next.js'te yapısal veri iki yerden gelebilir:
Layout dosyasından — tüm alt sayfalara uygulanır:
// app/urunler/[slug]/layout.tsx
<ProductJsonLd slug={slug} locale={locale} />
Sayfa dosyasından — o sayfaya özel:
// app/urunler/[slug]/page.tsx
const jsonLd = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "Nakliyat Yazılımı",
// ...
};
<script type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
Her ikisi de tek başına doğru. Birlikte olduklarında sayfada iki SoftwareApplication nesnesi bulunur.
Genelde şöyle oluyor: sayfa yazılırken şema doğrudan sayfaya konuyor. Sonra proje büyüyünce şema üretimi ortak bir bileşene taşınıyor ve layout'a bağlanıyor. Ama eski satır silinmiyor.
Aynı hatanın üç varyantı daha var ve hepsini sahada gördük:
Eklenti üstüne eklenti. WordPress'te bir SEO eklentisi Article üretir, tema da kendi Article'ını basar. İkisi de "ben hallederim" varsayar.
Çerez / analitik enjeksiyonu. Bazı üçüncü parti araçlar sayfaya kendi Organization şemasını ekler. Sizinkiyle çakışır ve genelde eksiktir.
Kopyala-yapıştır sayfa. Bir ürün sayfasını çoğaltıp içeriği değiştirirsiniz, ama şemadaki name alanı eski üründe kalır. Bu çakışma değil ama daha kötüsü: iki farklı sayfa Google'a aynı ürünü anlatır.
Neden sorun
Google aynı sayfada aynı türden birden çok varlık bulduğunda hangisinin doğru olduğunu bilmez.
- Fiyat farklıysa hangisini gösterecek?
- Puan farklıysa hangisine göre yıldız basacak?
- Ad farklıysa hangisini varlık olarak kaydedecek?

Google'ın yapısal veri belgeleri bu konuda net: bir sayfa bir ana varlığı tanımlamalıdır. Çelişki bulduğunda Google genelde en güvenli yolu seçer — hiçbirini kullanmaz. Zengin sonuç kaybı hata mesajıyla gelmez, sadece görünmez olursunuz.
Bizim durumumuzda iki şemadan biri fiyat bilgisi içermiyordu, diğeri offers alanı taşıyordu. Yani Google'a aynı ürün için hem "fiyatı var" hem "yok" diyorduk.
İkinci bir maliyet daha var ve bu artık daha da önemli: yapay zeka motorları da bu veriyi okuyor. Bir model "şu ürünün fiyatı ne" sorusuna cevap üretirken çelişkili iki değer bulursa ya ikisini de kullanmaz ya da yanlış olanı seçer. İkisi de sizin aleyhinize. ChatGPT'nin sitenizi neden okumadığını sıraladığımız listede altıncı madde tam olarak bu.
Nasıl tespit edilir
En hızlı yol tarayıcı konsolu:
document.querySelectorAll('script[type="application/ld+json"]').length
Birden fazla <script> olması sorun değil — sorun aynı @type'ın tekrarlanması. Türleri saymak için:
[...document.querySelectorAll('script[type="application/ld+json"]')]
.flatMap(s => {
const d = JSON.parse(s.textContent);
return d["@graph"] ? d["@graph"] : [d];
})
.map(x => x["@type"])
.reduce((acc, t) => (acc[t] = (acc[t] || 0) + 1, acc), {});
Beklenen çıktı şuna benzemeli:
{ SoftwareApplication: 1, BreadcrumbList: 1, FAQPage: 1, Organization: 1 }
Herhangi bir değer 1'den büyükse orada bir çakışma var.
Terminalden toplu kontrol için:
curl -s https://siteniz.com/urun-sayfasi \
| grep -o '"@type":"[A-Za-z]*"' | sort | uniq -c
Sitenin tamamını taramak isterseniz sitemap üzerinden dönün:
curl -s https://siteniz.com/sitemap.xml \
| grep -oE 'https://[^<]+' \
| while read u; do
say=$(curl -s "$u" | grep -o '"@type":"Product"' | wc -l | tr -d ' ')
[ "$say" -gt 1 ] && echo "$say $u"
done
Bu döngü, Product şeması birden fazla geçen sayfaları listeler. Product yerine kendi ana türünüzü yazın.
Üç doğrulama aracı da işinize yarar ama farklı şeyler söylerler:
| Araç | Ne söyler | Ne söylemez |
|---|---|---|
| Rich Results Test | Google'ın zengin sonuç için kullanabildiği türler | Çakışma olduğunu açıkça |
| Schema Markup Validator | schema.org'a göre sözdizimsel geçerlilik | Google'ın kullanıp kullanmayacağını |
| Search Console → Geliştirmeler | Sitedeki geçerli/hatalı öğe sayısı, zaman içinde | Tek tek hangi sayfada çakışma olduğunu |
Üçünü de kullanın ama karar merciini karıştırmayın: bir şema "geçerli" olabilir ve Google onu yine de kullanmayabilir.
Doğru yapı: tek graph
Birden çok şemayı ayrı <script> etiketlerine dağıtmak yerine tek bir @graph içinde toplayın:
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://siteniz.com/#kurum",
"name": "Örnek Ajans",
"url": "https://siteniz.com",
"logo": { "@type": "ImageObject", "url": "https://siteniz.com/logo.png" }
},
{
"@type": "SoftwareApplication",
"@id": "https://siteniz.com/urunler/x#urun",
"name": "Nakliyat Yazılımı",
"applicationCategory": "BusinessApplication",
"publisher": { "@id": "https://siteniz.com/#kurum" }
},
{
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Ürünler", "item": "https://siteniz.com/urunler" },
{ "@type": "ListItem", "position": 2, "name": "Nakliyat Yazılımı" }
]
},
{
"@type": "FAQPage",
"mainEntity": []
}
]
}
@id kullanmanın faydası: aynı kurumu her şemada tekrar tanımlamak yerine bir kez tanımlayıp diğerlerinden referans verirsiniz. Google varlıkları birbirine bağlar, siz de tekrar etmemiş olursunuz.
@id değerlerinde iki kural var. Mutlak URL olsun — göreli yol ya da rastgele bir kimlik, farklı sayfalardaki referansları birbirine bağlamaz. Sabit kalsın — kurum kimliğiniz her sayfada https://siteniz.com/#kurum ise Google tüm sayfalardaki referansları tek bir varlıkta toplar. Her sayfada farklı bir kimlik üretirseniz aynı kurumu yüzlerce ayrı varlık olarak görür.
Son maddedeki BreadcrumbList'in son öğesinde item alanının olmaması bilinçli: son basamak zaten bulunduğunuz sayfadır, kendine link vermesine gerek yok.
Tek üretim noktası olduğunda çakışma da mümkün olmaz.
Hangi şema türü ne işe yarar
Çakışmayı önlemenin ilk adımı, sayfaya gerçekten hangi türün gerektiğini bilmek. Fazla şema eklemenin sebebi genelde "ne lazım" sorusunun cevaplanmamış olmasıdır.
| Tür | Hangi sayfada | Somut karşılığı |
|---|---|---|
Organization |
Site genelinde bir kez | Bilgi paneli, marka eşleştirme |
LocalBusiness |
İletişim / şube sayfası | Harita, çalışma saati, arama butonu |
Article / BlogPosting |
Blog yazıları | Tarih, yazar, haber görünürlüğü |
Product |
Ürün sayfası | Fiyat, stok, yıldız |
SoftwareApplication |
Yazılım ürünü sayfası | Kategori, platform, fiyat |
FAQPage |
SSS içeren sayfa | Sonuçta açılır soru listesi |
BreadcrumbList |
Tüm iç sayfalar | Sonuçta URL yerine kırılım yolu |
HowTo |
Adım adım rehber | Adım listesi görünümü |
Bir sayfada tipik olarak üç ila dört tür bulunur: Organization (referansla), sayfanın ana türü, BreadcrumbList ve varsa FAQPage. Bundan fazlası genelde birikmiş artıktır.
Bir uyarı: Google'ın zengin sonuç desteği zaman içinde değişiyor. FAQPage ve HowTo görünümleri geçmişte daraltıldı; bugün hangi türün nasıl gösterildiğini Google'ın kendi zengin sonuç galerisinden kontrol edin. Şemayı yazma sebebiniz yalnızca görsel zengin sonuç olmasın — makine okunabilirliğin kendisi zaten değerli.
Article mı BlogPosting mi
Sık sorulan ve gereğinden fazla dert edilen bir soru.
BlogPosting, Article'ın alt türüdür. Yani BlogPosting yazdığınızda Google onu zaten Article olarak da anlar. Blog yazısı için BlogPosting, haber için NewsArticle, kurumsal bir doküman ya da rehber için Article mantıklı seçimlerdir — ama aralarındaki fark, alanların doğru doldurulmasının yanında önemsiz kalır.
Gerçekten fark yaratan alanlar şunlar:
{
"@type": "BlogPosting",
"headline": "Sayfadaki H1 ile aynı olmalı",
"datePublished": "2026-08-17T10:00:00+03:00",
"dateModified": "2026-08-30T14:20:00+03:00",
"author": { "@type": "Person", "name": "Ad Soyad", "url": "https://siteniz.com/hakkimizda" },
"publisher": { "@id": "https://siteniz.com/#kurum" },
"inLanguage": "tr",
"mainEntityOfPage": { "@type": "WebPage", "@id": "https://siteniz.com/blog/yazi" }
}
Dört noktaya dikkat:
headline sayfadaki başlıkla aynı olsun. Farklı yazmak, "şemadaki bilgi sayfada görünmeli" kuralının ihlalidir.
dateModified gerçek olsun. Her dağıtımda "şimdi" yazan bir dateModified, sitemap'te her URL'ye aynı lastmod'u yazmakla aynı güven sorununu yaratır.
author bir varlığa bağlansın. Sadece isim yazmak yetersiz; url ya da @id ile gerçek bir sayfaya bağlayın. Yapay zeka motorlarının "bunu kim söylüyor" sorusuna verdiği cevap burası.
publisher @id ile referans verilsin, kurum bilgisi tekrar yazılmasın.
Bizim blog sayfalarımızda bu graph şöyle kuruluyor: Article → author (Person) → worksFor (Organization), yanında publisher, image, breadcrumb ve varsa FAQPage. Hepsi tek @graph içinde, @id referanslarıyla bağlı. Ayrı ayrı duran düz varlıklar bağlı bilgi üretmiyor.
İkinci tuzak: sayfada olmayan şey şemada olmamalı
Denetimde karşılaştığımız ikinci sorun buydu.
Yapısal veri, sayfada görünen içeriği makine okunur hale getirmek içindir. Sayfada bulunmayan bir bilgiyi şemaya yazmak — fiyat, puan, yorum sayısı — Google'ın yapısal veri politikalarına aykırıdır ve manuel işlem sebebidir.
Bizim ürün sayfalarımızda fiyat teklif usulü belirleniyor, sayfada fiyat yazmıyor. Bu yüzden şemada offers alanını bilinçli olarak boş bıraktık. Uydurma bir fiyat yazmak zengin sonuç getirebilirdi ama riski taşımaya değmez.
Aynı kural yorumlar için de geçerli: sayfada görünmeyen AggregateRating yazmayın. Gerçek olmayan yorum yayınlamak zaten ayrı bir sorun.
Bu maddede en sık düşülen hata AggregateRating'i "kendi hakkında" yazmak: kendi sitenizde kendi hizmetinize beş yıldız vermek. Google'ın kuralı açık — bir varlık kendi kendini derecelendiremez.
Üçüncü tuzak: alan adı uyuşmazlığı
Bu, bizim kendi kodumuzda bulduğumuz ve en sevdiğimiz hata.
SSS verileri veritabanında {q, a} alanlarıyla saklanıyordu. Şema üreten kod ise {question, answer} okuyordu. Kod hata vermiyordu — JavaScript'te olmayan bir alanı okumak undefined döner, çökme olmaz.
Sonuç: sekiz yazıda FAQPage şeması şöyle yayınlanıyordu:
{
"@type": "Question",
"name": undefined,
"acceptedAnswer": { "@type": "Answer", "text": undefined }
}
Geçersiz JSON, geçersiz şema, hiçbir uyarı yok. Sayfa açılıyor, her şey normal görünüyor.
Bu tür hataları yakalamanın en hızlı yolu ham çıktıda undefined aramaktır:
curl -s https://siteniz.com/blog/yazi | grep -c "undefined"
Sıfırdan büyük her sonuç incelenmeli. Aynı kontrolü "null" ve "[object Object]" için de yapın — üçü birlikte, şablon üreten kodun en sık verdiği üç sessiz hatayı kapsar.
Dördüncü tuzak: çok dilli sitede kopyalanan şema
Türkçe ve İngilizce sürümü olan bir sayfada şemayı da çevirmeniz gerekir — ve genelde yarısı çevrilir.
Tipik tablo: headline ve description çevrilmiş, ama inLanguage her iki sürümde de "tr" kalmış. Ya da BreadcrumbList içindeki basamak adları Türkçe, URL'ler İngilizce sürümü gösteriyor. İkisi de Google'a "bu sayfa hangi dilde emin değilim" dedirtir.
Kontrol basit:
for l in tr en; do
echo "── $l"
curl -s https://siteniz.com/$l/blog/yazi | grep -o '"inLanguage":"[a-z-]*"'
done
İki satır farklı dönmeli. Aynı dönüyorsa şema tek dilden kopyalanmış demektir.
Aynı sayfada hreflang etiketlerinin de tutarlı olması gerekir. Var olmayan bir çeviriye hreflang vermek, hiç vermemekten kötüdür: Google karşılıklılık bulamazsa kümenin tamamını yok sayabilir. Bizim şemamızda çeviri bağı translationKey alanıyla kuruluyor ve hreflang yalnızca gerçekten eşleşen çift varsa yayınlanıyor — tekil yazılarda hiç yayınlanmıyor.
Çakışmayı önleyen mimari
Tek seferlik temizlik yeterli değil; altı ay sonra biri yine sayfaya şema ekler. Kalıcı çözüm yapısal:
1. Şema üretimi tek bir modülde toplansın. src/lib/schema.ts gibi tek bir dosya, sayfa tipine göre @graph üretsin. Sayfalar JSON yazmasın, fonksiyon çağırsın.
2. Sayfa bileşenleri ham <script> yazmasın. Kod incelemesinde aranacak tek şey: application/ld+json metni sayfa dosyalarında geçiyor mu. Geçiyorsa yanlış katmandadır.
3. Yayın öncesi otomatik kontrol. Basit bir betik, derlenmiş sayfalarda @type tekrarını sayıp ikiden fazlaysa uyarsın. Bu bir sayfada beş dakikalık iş, yüz sayfada elle imkânsız.
4. Şema değişince Rich Results Test'ten geçirin. Alışkanlık haline gelmesi gereken tek manuel adım.
Doğrulama: düzeldiğini nasıl anlarsınız
Düzeltmeyi yaptınız, peki işe yaradı mı?
Hemen: Rich Results Test'te sayfa tek bir ana varlık gösteriyor, hata ve uyarı yok.
Birkaç gün içinde: Search Console'da URL denetimi yapıp "Test edilen sayfa" görünümünde şemayı kontrol edin — Google'ın gördüğü hali budur, sizin tarayıcınızın gördüğü değil.
Haftalar içinde: Search Console → Geliştirmeler bölümünde ilgili öğe türünün "geçerli" sayısı yükselir. Bu rakamın hareket etmesi zaman alır; tarama sıklığınıza bağlıdır. Sayfalarınızın ne kadar çabuk yeniden tarandığını sitemap lastmod yazısında anlattığımız tarama bütçesi sinyali belirliyor.
Aylar içinde: Zengin sonuçların arama sonuçlarında görünmesi. Burada bir uyarı: geçerli şema zengin sonuç garantisi değildir. Google şemayı okur, doğru bulur ve yine de göstermemeyi seçebilir. Şema koşuldur, garanti değil.
Kontrol listesi
Bir sayfayı yayına almadan önce:
- Her
@typesayfada tek kez geçiyor mu - Şema tek bir yerden mi üretiliyor (layout ya da sayfa, ikisi birden değil)
- Şemadaki her bilgi sayfada görünüyor mu
-
name,descriptiongibi alanlar sayfadaki metinle tutarlı mı - Ham çıktıda
undefined,nullya da[object Object]geçmiyor -
@iddeğerleri mutlak URL ve sayfalar arasında sabit - Telefon, adres, e-posta gibi bilgiler sitenin geri kalanıyla aynı mı
Son madde önemsiz görünür ama değil. Bizim sitemizde telefon numarası üç ayrı yerde yer tutucu olarak duruyordu ve üçü de birbirinden farklıydı: şemada bir numara, tel: bağlantısında başka, görünen metinde bir başkası. Google'ın kurumsal varlık eşleştirmesi bu tutarsızlıkları yakalar — bu konuyu NAP tutarlılığı yazısında ayrı ele aldık.
Özet
Yapısal veri "ne kadar çok o kadar iyi" değildir. Bir sayfa bir şey anlatmalı ve o şeyi bir kez anlatmalıdır.
Şemayı ortak bir bileşene taşıdığınızda eski satırı silmeyi unutmayın — çünkü unuttuğunuzda hiçbir hata almazsınız, sadece zengin sonuçlarınız sessizce kaybolur.
Ürün ve fiyat şemasının doğru kurulması özellikle e-ticarette doğrudan tıklama farkı yaratıyor; o tarafın kurulum sırasını e-ticaret sitesi nasıl kurulur yazısında topladık. Şema katmanının yapay zeka görünürlüğüyle ilişkisi için GEO rehberindeki schema graph bölümüne bakın. Kendi sayfalarınızda bu denetimi bizim yapmamızı isterseniz hizmetler sayfasında kapsam yazılı.
Sıkça sorulan sorular
Birden çok `<script>` etiketi olması sorun değil. Sorun aynı `@type`'ın tekrarlanmasıdır. Sayfada iki `SoftwareApplication` ya da iki `Product` varsa Google hangisinin doğru olduğunu bilemez ve genelde en güvenli yolu seçer: hiçbirini kullanmaz.
Tarayıcı konsolunda tüm ld+json bloklarını okuyup `@type` değerlerini sayın; her tür bir kez geçmeli. Terminalden hızlı kontrol: `curl -s https://siteniz.com/sayfa | grep -o '"@type":"[A-Za-z]*"' | sort | uniq -c`. Herhangi bir sayı 1'den büyükse orada çakışma var.
Hayır. Yapısal veri, sayfada görünen içeriği makine okunur hale getirmek içindir. Sayfada olmayan fiyat, puan ya da yorum sayısını şemaya yazmak Google'ın yapısal veri politikalarına aykırıdır ve manuel işlem sebebidir. Fiyatınız teklif usulü belirleniyorsa `offers` alanını boş bırakın.
İkisinden birine — ikisine birden değil. Ortak bir bileşen yazıp layout'a bağlamak en temizi, çünkü tek üretim noktası olur ve çakışma mümkün olmaz. Ama şemayı ortak bileşene taşıdığınızda sayfadaki eski satırı silmeyi unutmayın; unuttuğunuzda hiçbir hata almazsınız, sadece zengin sonuçlarınız sessizce kaybolur.