feat(geo): llms.txt, RSS, IndexNow, Article yazar/görsel, bölüm sayfasına veri özeti, vakıf bursu yazısı
All checks were successful
Deploy / deploy (push) Successful in 6m16s

GEO araştırması (docs/arastirma/2026-09-24-geo-seo) Dalga 1-2 uygulaması:
- /llms.txt ve /feed.xml route'ları (getAllRehberler'den otomatik), head'de RSS linki
- Article JSON-LD: author, image, mainEntityOfPage, publisher logo; görünür 'Yazan' satırı
- robots: ClaudeBot ayrı grupta Crawl-delay 1
- scripts/indexnow.ts (pnpm indexnow) + public anahtar dosyası
- bölüm sayfası açılışı veriden üretilen özgü cümleler (il/program/tür/kontenjan, taban aralığı, yıllık yön)
- ana sayfa fiyat iddiasına kaynak; iki yazıda 23.000 -> 21.000+; beş yazıya 'Kısa cevap' açılışı
- yeni yazı: vakif-universitesi-bursu-kesilir-mi (ÖSYM 2026 kılavuzu + YÖK ücret esasları)
- YAYIN-KUYRUGU: IndexNow adımı ve aylık AI görünürlük rutini

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-24 17:42:29 +03:00
parent 6094066c5e
commit 24d4bea32c
24 changed files with 1202 additions and 11 deletions

View File

@@ -16,6 +16,7 @@ import {
getBolumBySlug,
uniSlugFromAd,
} from "../katalog-queries";
import type { Program } from "@/types/yokatlas";
import {
JsonLd,
breadcrumbJsonLd,
@@ -156,6 +157,63 @@ function siraAraligiMetni({
return verisiz > 0 ? `${aralik} ile taban verisi olmayanlar` : aralik;
}
/**
* İlk sayfanın açılış cümleleri: kaç ilde kaç program, devlet/vakıf dağılımı,
* toplam kontenjan, taban sıralaması aralığı ve bir önceki yıla göre yön.
* Yön için yalnızca iki yılda da sırası olan programlar sayılır; sıra sayısı
* büyümüşse o programa daha geriden yerleşilmiş demektir (öğrenci dilinde
* "gevşedi"). Beş programdan azsa yön cümlesi kurulmaz.
*/
function bolumVeriOzeti(bolum: BolumDetay, programlar: Program[]): string {
const { stats } = bolum;
const iller = new Set<string>();
let kontenjan = 0;
let gevseyen = 0;
let sikilasan = 0;
let karsilastirilan = 0;
for (const p of programlar) {
if (p.il) iller.add(p.il);
kontenjan += p.kontenjanSon ?? 0;
const onceki = p.siraGecmisi[p.siraGecmisi.length - 2];
if (p.siraSon != null && onceki != null) {
karsilastirilan += 1;
if (p.siraSon > onceki) gevseyen += 1;
else if (p.siraSon < onceki) sikilasan += 1;
}
}
const digerTur = stats.toplam - stats.devlet - stats.vakif;
const dagilim = [
stats.devlet > 0 ? `${sayi(stats.devlet)} devlet` : null,
stats.vakif > 0 ? `${sayi(stats.vakif)} vakıf` : null,
digerTur > 0 ? `${sayi(digerTur)} KKTC/yurt dışı` : null,
]
.filter(Boolean)
.join(", ");
const ilSayisi = iller.size > 0 ? iller.size : stats.ilSayisi;
const cumleler = [
`${bolum.ad} bölümü ${SON_YIL} YKS yerleştirmesinde ${sayi(ilSayisi)} ilde ${sayi(stats.toplam)} programda öğrenci aldı (${dagilim}${kontenjan > 0 ? `; toplam ${sayi(kontenjan)} kontenjan` : ""}).`,
];
if (stats.minSira != null && stats.maxSira != null) {
cumleler.push(
stats.minSira === stats.maxSira
? `Taban başarı sıralaması ${sayi(stats.minSira)}.`
: `Taban başarı sıralaması programa göre ${sayi(stats.minSira)} ile ${sayi(stats.maxSira)} arasında değişiyor.`,
);
}
if (karsilastirilan >= 5) {
// Yüzdeye iyelik eki gerekmeyen kalıp: "%7'sinde / %9'unda" gibi ek
// seçimi sayının okunuşuna bağlı, şablonda güvenle kurulamaz.
const kucOran = Math.round((sikilasan / karsilastirilan) * 100);
const buyOran = Math.round((gevseyen / karsilastirilan) * 100);
cumleler.push(
kucOran >= buyOran
? `${ONCEKI_YIL}'e göre taban sıralaması sayı olarak küçülen (yerleşmesi zorlaşan) program oranı %${kucOran}, büyüyen (kolaylaşan) %${buyOran}.`
: `${ONCEKI_YIL}'e göre taban sıralaması sayı olarak büyüyen (yerleşmesi kolaylaşan) program oranı %${buyOran}, küçülen (zorlaşan) %${kucOran}.`,
);
}
return cumleler.join(" ");
}
export function bolumSayfaMetadata(slug: string, sayfa: number): Metadata {
const bolum = getBolumBySlug(slug);
if (!bolum) return {};
@@ -237,6 +295,11 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
}
}
const { stats } = bolum;
// Sayfaya özgü, veriden türeyen özet cümle (GEO: yapay zekâ cevap motorları
// sayfa başına ilk metni saklar; 1011 katalog sayfasının aynı şablon cümleyle
// açılması hem alıntıyı hem Google'ın "ölçekli içerik" politikasını zorlar —
// docs/arastirma/2026-09-24-geo-seo). Rakamlar tablodakiyle aynı kaynaktan.
const veriOzeti = ilkSayfa ? bolumVeriOzeti(bolum, tumProgramlar) : null;
const hapSinifi =
"inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-800";
@@ -325,9 +388,8 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
{ilkSayfa ? (
<p className="mx-auto mt-6 max-w-3xl text-center text-base leading-relaxed text-slate-600 sm:text-lg lg:mx-0 lg:text-left">
{bolum.ad} bölümünü açan {sayi(tumProgramlar.length)} üniversite
programının {SON_YIL} taban puanlarını, başarı sıralamalarını ve
kontenjanlarını incele.{" "}
{veriOzeti} Aşağıdaki tabloda her programın taban puanı, başarı
sıralaması ve kontenjanı var.{" "}
<em>
{TERCIH_YILI} tabanları, yerleştirme sonuçları açıklandığında
netleşir.

View File

@@ -74,7 +74,8 @@ export function RehberMakale({ slug }: { slug: string }) {
{yazi.aciklama}
</p>
<p className="mt-3 text-xs text-slate-500">
Yayın: <time dateTime={yazi.tarih}>{trTarih(yazi.tarih)}</time>
Yazan: KolayTercih Ekibi · Yayın:{" "}
<time dateTime={yazi.tarih}>{trTarih(yazi.tarih)}</time>
{yazi.guncelleme ? (
<>
{" · "}Güncelleme:{" "}